基类指针的派生类方法:一些替代方法

Derived class method from base class pointer : some alternatives?

本文关键字:方法 指针 派生 类方法 基类      更新时间:2023-10-16

我知道这类问题已经回答过几次了,但是我给出这个问题的上下文是为了期待一些其他的体系结构替代方案。

考虑一个类CExpression:

class CExpression
{
    public:
        ...
    private:
        vector<CComponent*> components_;
        string expression_;
}

CExpression必须分解字符串,它表示一个数学表达式(例如:"y = x + 5")转换成vector ("y", " = ", "x", " + ", 5)。为此,vector由CComponent指针组成,这些指针可以指向CVariable, COperator和CConstant类的对象。显然,CComponent是一个抽象类,也是上面提到的三个类的基类。因此,在解析字符串之后,向量应该按顺序包含以下内容(过程的半伪代码):

components_.push_back(new CVariable("y"));
components_.push_back(new COperator('='));
components_.push_back(new CVariable("x"));
components_.push_back(new COperator('+'));
components_.push_back(new CConstant( 5 ));
这里多态性的使用是将表达式分解为单个向量(这将有利于将来的解析过程)。然而,一些派生类具有其他类所没有的独特特性,使得不可能在基类(CComponent)中实现这些特性。例如,考虑COperator类:
class COperator : public CComponent
{
    public:
        int GetPriority() const { return prority_; }
        ...
    private:
        int priority_;
        ...
}

Priority表示操作符必须从vector对象解析的优先级,是该类唯一的(因此基类中没有虚函数)。现在让我们来解决这个问题。

考虑CComponent类(基类):
enum Type { VARIABLE, OPERATOR, CONSTANT };
class CComponent
{
    public:
        Type GetType() const { return type_; }
        ...
    private:
        Type type_;
        ...
}

Type,对于表达式的任何组件都是通用的,表示组件的类型(例如,如果它是一个CVariable,那么在构造时类型将被设置为VARIABLE)。

最后,考虑这个CExpression方法(虚构的):
void CExpression::Process()
{
    for (int i = 0; i < components_.size(); i++)
    {
        if (components_[i] -> GetType() == OPERATOR)
        {
            cout << components_[i] -> GetPriority(); // won't work
        }
    }
}

实际上,由于我只能使用指针类型类的方法(除非我使用dynamic_cast,我认为这不是最漂亮的方式),我有两个问题:

  1. 是否有适当的方法来做我想要实现的或dynamic_cast是我唯一的选择?
  2. 我应该采用完全不同的程序架构来解决这个问题吗?如果是,我该怎么办?

顺便说一下,我知道这可以更简单地解释,但我认为上下文将是一个伟大的帮助抓住问题。

谢谢!

我猜,你的架构不会满足你的需求——尤其是当你开始扩展它的时候。

我在处理数学表达式方面有一些经验,我想说,最自然的存储表达式的方式是树。每个终端项(如数字或变量)是树的一个叶子,每个非终端项(如操作符或函数调用)是一个节点,它有子节点。例如:

y = x + 5

应该翻译成tree:

  =
 / 
y   +
   / 
  x   5

这种结构的好处是什么?首先,它比令牌向量更容易求值。其次,诸如操作符优先级或关联方向之类的东西仅在构建此结构时才有意义——在构建结构并准备进行评估时不会使用它们。然后,每个节点不关心作为子节点附加到它的内容,它只是让它们评估自己,当完成后,它最终得到一个可以工作的终端项目列表。甚至赋值操作符也可以执行它的工作(当然,如果你传递给它某种包含变量列表的上下文)。

如果你使用著名的反向波兰符号算法,创建这样的结构是非常容易的。

在你的情况下,我投票赞成完全重新排列你的数据结构,这是一个更好的存储表达式。

还有一件事同样根据我的经验,我强烈建议您为这三件事创建不同的类:

  • 从输入中读取的项(=令牌)
  • 表达式树中包含的项(=一个表达式项)
  • 在计算树时作为部分结果的项(= eval对象)

这看起来可能会使您的体系结构复杂化,但实际上会简化您的工作,并使您的体系结构更加灵活。

结构草图

class BaseNode
{
public:
    virtual EvalObject Eval() = 0;
    // This method is handy when working with assignment operator.
    // For instance, Eval() called on variable will return its value
    // but EvalLHS() will return a reference to variable. 
    virtual EvalObject EvalLHS() = 0;
};
class Operator : BaseNode
{
};
class BinaryOperator : Operator
{
private:
    BaseNode * leftChild;
    BaseNode * rightChild;
};
class Add : BinaryOperator
{
public:
    void Eval()
    {
        auto left = leftChild->Eval(); // Eval RHS, 
        auto right = rightChild->Eval(); // Eval RHS
        // Now perform calculations on left and right
        // depending on their types
    }
    void EvalLHS()
    {
        throw InvalidOperationException("Cannot perform LHS evaluation on adding operator");
    }
}
class Assign : BinaryOperator
{
public:
    void Eval()
    {
        auto left = leftChild->EvalLHS();
        auto right = rightChild->Eval();
        // Perform assignment
        // This is required such that operations
        // like a = b = 7 will also work
        return right;
    }
    void EvalLHS()
    {
        // Assignment cannot be on the LHS of operation, eg.
        // (a = 5) = 8 is wrong
        throw InvalidOperationException("Assignment cannot be LHS");
    }
}

我认为设计很差,在这里我将解释原因:
当您进入enum Type时,您实际上承认,尽管您希望拥有一个纯接口,可以在上同样地任何派生类上工作,但您无法做到。在基类中实现的算法确实需要知道派生类的确切类型才能发挥其作用。在这方面,enum Typedynamic_cast服务于相同的习语:依赖于确切类型的实现。
这不是面向对象的方式

面向对象的方式声称你的算法,你的代码,用一些基类作为输入的函数,对接口背后的实际对象没有任何假设——只有它的接口重要。
至于你的具体问题,正如上面提到的,我也认为树状结构最适合这个问题。有几种方法可以做到这一点。在我看来,处理它有两个阶段:1。建筑结构;2. 对结构做一些评估。
我将尽量只勾勒(不编译)我的想法,细节和微调留给你:

class Expression
{
};    
class Constant : public Expression
{
    public:
        // 'int' can be easily changed to generic type 'T'
        Constant( int value ) : _value( value ) {}
    private:
        int _value;
};
class Operator : public Expression
{
    public:
        Operator( Expression left, Expression right )
            : _left( left ), _right( right ) {}
    protected:
        Expression _left;
        Expression _right;
};
class OperatorPlus : public Expression
{
    public:
        OperatorPlus( Expression left, Expression right )
            : Operator(_left( left ), _right( right )) {} 
};
// few more operators, the same
class OperatorMinus : public Expression { /* ... */ }
class OperatorMul : public Expression { /* ... */ }
class OperatorDiv : public Expression { /* ... */ }

class Variable : public Expression
{
    public:
        // string can be easily changed to generic type 'T'
        Constant( string value ) : _value( value ) {}
    private:
        string _value; 
}

void f()
{
    // y = x + 5
    OperatorEqual s1( Variable( "y" ), OperatrPlus( Variable( "x" ), Constant( 5 ) ) );
}
至于实际上用它做一些事情。我认为最好的方法是在Expression类中添加所需的功能:
class Expression
{
    public:
        virtual Expression eval() = 0;
}

和派生可以很容易地实现它:

class OperatorPlus : public Expression
{
    public:
        OperatorPlus( Expression left, Expression right )
            : Operator(_left( left ), _right( right )) {} 
    virtual Expression eval()
    {
        return _left.eval() + _right.eval();
    }
}

Expression的其他接口可能是printshortencombineEqual或任何适合您的域