【问题标题】:C++ Design issue for expression computing表达式计算的 C++ 设计问题
【发布时间】:2014-05-08 11:33:02
【问题描述】:

我需要创建类来表示表达式,例如someVar=2*(5+3)^5。为此,我想要以下课程:

class IExpression
{
    public:
        virtual ~IExpression( void ) { }
        virtual CValue eval( const CScope & ) const = 0;
};

class CConstant : public IExpression
{
    protected:
        const CValue _value;
    public:
        virtual CValue eval( const CScope & ) const { return this->_value; }
};

class COperation : public IExpression
{
    protected:
        const IExpression * const _left;
        const operator_e          _eOper;
        const IExpression * const _right;
    public:
        virtual ~COperation( void );
        virtual CValue eval( const CScope & ) const;
};

class CVariable : public IExpression
{
    protected:
        const string _name;
    public:
        virtual CValue eval( const CScope & scope ) const { return scope.access( this->_name ); }
};

还假设我有一个类CScopeCVariable::eval() 使用它来访问变量:

class CScope
{
    protected:
        map<string, CValue> _mVars;
    public:
        inline const CValue & access( const string & name ) const;
        inline       CValue & access( const string & name );
};

我这里有问题。想象一下,我有一个使用运算符的操作,该运算符需要通过引用访问该值,以便可以设置它,例如=+=。对于我会做的任务:

CValue COperation::eval( const CScope & scope ) const
{
    switch( this->_eOper )
    {
        // ...
        case E_ASSIGN:
            return this->_left->eval( scope ) = this->right->eval( scope );
    }
}

这里的问题是this-&gt;_left-&gt;eval( scope ),其中_left 指向CVariable,将返回由该变量名称标识的CValue 的副本,并且分配不会进行上班。

我可以更改方法 eval() 的签名以使其返回 CValue &amp;,但这会导致问题,因为在 COperation 的情况下,操作的结果是一个临时变量,它被销毁为COperation::eval() 返回。

你知道我该怎么做吗?

【问题讨论】:

  • 您的作业的 LHS 允许哪些内容?只有变量还是某些类型的表达式?
  • 这让我想起了圆椭圆问题:赋值是一种需要左操作数可变性的操作。也就是说,赋值不仅仅是一个操作,它有更严格的要求。
  • @dyp 在操作的左侧,允许 IExpression 的任何子类。如果你使用一个常量,它应该分配一个该常量的副本而不做任何事情,对于操作的结果也是如此,如果你使用一个变量,它应该分配一个对该变量的引用。至少这是我想做的。你认为我应该分开处理作业吗?
  • @ReCaptcha 不,我将 CScope 更改为 COperation。我写的时候搞错了。

标签: c++ interface reference


【解决方案1】:

从 cmets 的讨论到 OP:

42 = 2 会发生什么?

CConstant::eval() 将返回将被分配的成员 CValue 的临时副本,但这不会做任何事情,因为它将是一个临时值,而不是将被分配的成员本身。就像这样做:3 + 5 = 10;

我不是特别喜欢这种设计,因为它允许容易出错或无意义的表达,但让我们尝试实现它:

class IExpression
{
    public:
        virtual ~IExpression( void ) { }
        virtual CValue& eval( CScope & ) const = 0;
};

使用这个接口,我们可以改变表达式的任何返回值。当然,它需要使用临时对象。您可以将它们推送到CScope 中的堆栈,这需要修改作用域对象。例如:

class CConstant : public IExpression
{
    protected:
        const CValue _value;
    public:
        virtual CValue& eval( CScope & scope ) const
        {
            return scope.push(_value);
        }
};

变量大致保持不变:

class CVariable : public IExpression
{
    protected:
        const string _name;
    public:
        virtual CValue& eval( CScope & scope ) const
        { return scope.access( this->_name ); }
};

那么,赋值就按照你写的方式工作了:

CValue COperation::eval( CScope & scope ) const
{
    switch( this->_eOper )
    {
        // ...
        case E_ASSIGN:
            return this->_left->eval( scope ) = this->right->eval( scope );
    }
}

如果您不想始终创建值的副本,则可以在赋值的 LHS 处使用常量时限制复制。

class IExpression
{
    public:
        virtual ~IExpression( void ) { }
        virtual CValue rvalue( CScope const& ) const = 0;
        virtual CValue& lvalue( CScope & ) const = 0;
};

class CConstant : public IExpression
{
    protected:
        const CValue _value;
    public:
        virtual CValue& lvalue( CScope & scope ) const
        {
            return scope.push(_value);
        }
        virtual CValue rvalue( CScope const& scope ) const
        {
            return _value;
        }
};

CValue COperation::eval( CScope & scope ) const
{
    switch( this->_eOper )
    {
        // ...
        case E_ASSIGN:
            return this->_left->lvalue( scope ) = this->right->rvalue( scope );
    }
}

我们也可以更本地地创建临时对象:

class CMutableValue
{
private:
    union { CValue _cpy; };
    bool _tmp;
public:
    CValue& _ref;

    CMutableValue(CValue & var)
        : _tmp(false), _ref(var) {}
    CMutableValue(CValue const & val)
        : _cpy(val), _tmp(true), _ref(_cpy) {}
    CMutableValue(CMutableValue const& source)
        : _tmp(source._tmp), _ref( source._tmp ? _cpy : source._ref )
    {
        if(source._tmp) new( (void*)&_cpy ) CValue(source._ref);
    }

    // delete in C++11, or private in C++03
    CMutableValue& operator=(CMutableValue const&) = delete;
};

这试图避免临时工的一些终身问题,但我认为这仍然很危险。

class IExpression
{
    public:
        virtual ~IExpression( void ) { }
        virtual CMutableValue eval( CScope const& ) const = 0;
};

class CConstant : public IExpression
{
    protected:
        const CValue _value;
    public:
        virtual CValue& eval( CScope & scope ) const
        {
            return CMutableValue(_value);
        }
};

CValue COperation::eval( CScope & scope ) const
{
    switch( this->_eOper )
    {
        // ...
        case E_ASSIGN:
            return   this->_left->eval( scope )._ref
                   = this->right->eval( scope )._ref;
    }
}

【讨论】:

  • 临时对象可以像在 C++ 中一样在完整表达式的末尾弹出。
  • 非常感谢您的帮助!我想到了一些看起来有点像你所做的事情:virtual const core::CValue &amp; eval( const core::CScope &amp; __roScope ) const = 0;virtual core::CValue &amp; eval( core::CScope &amp; __roScope ) const = 0; 我要研究你的答案。顺便说一句,为什么 CScope const&amp; scope 而不是 const CScope &amp; ?引用不是自然不变的吗?
  • 嗯,我发现的似乎不太好......我会坚持你的回答! :)
  • CScope const&amp; scopeconst CScope &amp; scope 是等价的。我更喜欢前者,因为它更符合const 的其他用途。当始终使用第一个版本时,您可以将 const 推断为 const 之前的东西是不变的。例如,int const*int* const
  • 好吧,我不知道我们会这样做。
【解决方案2】:

添加一个方法GetRefCValue 用于分配,如果CValue 不是左值,则让它抛出,它必须跟踪。
或者创建一个引用类,它是CValue 的子类型,并在分配中dynamic_cast

【讨论】:

  • 感谢您的回答。在我目前设计事物的方式中,CValue 不知道或者它是操作员的哪一边。 CValue 只是一段数据(联合 + 数据类型)。然而,IExpression 子类重新表示表达式的骨架,并且不受更改的影响,因为源代码在运行时不会更改。我不确定你的想法。
  • 我的意思是,让CVariable::eval 通过子类型或成员返回一种特殊的CValue,它知道它是左值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多