【问题标题】:C++ operator overloading in recursive expression classes递归表达式类中的 C++ 运算符重载
【发布时间】:2014-01-20 15:38:12
【问题描述】:

我有一个递归类Expression,它表示类似布尔的表达式,例如:

(a & b) | (c & ~d)

请注意,Expression 同时处理一元和二元表达式。 基本上,Expression 应该遵循类似于布尔表达式的 CFG。

我是这样设计课程的:

class Expression {
public:
    Expression() = default;
    Expression(unique_ptr<Expression> lhs, unique_ptr<Expression> rhs,
        unique_ptr<IBinaryOperator> binop, unique_ptr<IUnaryOperator> unop);
    Expression operator^(Expression& that);
    Expression operator%(Expression& that);
    Expression operator|(Expression& that);
    Expression operator*(Expression& that);
    Expression operator+(Expression& that);
    Expression operator&(Expression& that);
    Expression operator>>(Expression& that);
    Expression operator!();
    Expression operator~();
    double Evaluate(double x);
    virtual ~Expression();
protected:
    unique_ptr<Expression> _lhs = nullptr;
    unique_ptr<Expression> _rhs = nullptr;
    unique_ptr<IBinaryOperator> _binop = nullptr;
    unique_ptr<IUnaryOperator> _unop = nullptr;
};

构造函数的实现以及二元和一元运算符各一个如下所示:

Expression::Expression(unique_ptr<Expression> lhs, unique_ptr<Expression> rhs, unique_ptr<IBinaryOperator> binop, unique_ptr<IUnaryOperator> unop) :
        _lhs(move(lhs)), _rhs(move(rhs)), _binop(move(binop)), _unop(move(unop)) {
}
Expression Expression::operator+(Expression&& that) {
    return Expression(unique_ptr<Expression>(this), unique_ptr<Expression>(&that), unique_ptr<IBinaryOperator>(new SumCoNorm), nullptr);
}
Expression Expression::operator~() {
    return Expression(nullptr, unique_ptr<Expression>(this), nullptr, unique_ptr<IUnaryOperator>(new Intensify));
}

类编译失败

error: use of deleted function 'Fuzzy::Expression::Expression(const Fuzzy::Expression&amp;)'

在每个重载的运算符中(在返回语句中)。 我感觉某些函数在内部尝试使用不存在的 unique_ptr 的复制构造函数。我在这里和那里移动指针有什么问题吗?我将 C++11 与 GCCv4.8 一起使用。

欢迎以任何方式对类接口的更改提出建议。我宁愿避免使用原始指针。

注意:请不要建议使用解析器生成器等,例如 Boost.Spirit、YARD 或 YACC。该应用程序需要我从头开始实现。

【问题讨论】:

  • 我承认自己生疏了,但我觉得你将构造函数的参数移动到对象的数据成员中似乎很奇怪。
  • 没关系;现在我看到这些是本地的unique_ptr&lt;&gt;s。我希望它们是参考。

标签: c++ c++11 operator-overloading unique-ptr recursive-descent


【解决方案1】:

从概念上来说,

return Expression(...);

创建一个新的Expression 对象,然后将其复制或移动到返回值。在您的情况下,您没有移动构造函数(没有隐式移动构造函数,因为您有一个用户声明的析构函数)和一个已删除的复制构造函数,所以这是不可能的。

你可以使用

return {...};

为了避免复制/移动操作,或者你可以确保你有一个移动构造函数:

class Expression {
public:
    Expression() = default;
    Expression(Expression &&) = default;
...
};

附加说明,来自 Ben Voigt 的 cmets 正确地指出这使它可以编译,但实际上不起作用:

unique_ptr 仅适用于使用new 分配的对象,除非您使用自定义删除器。在你的情况下,它是行不通的,你需要重新考虑你的逻辑。

我认为你应该只保留unique_ptr 作为实现细节,而不是让外部调用者担心它。如果您确保您的Expression 是可复制和可移动的,您应该可以根据需要从Expression 中动态分配对象以存储为_lhs 和_rhs。以std::vector&lt;T&gt; 为例,您不需要new 来使用它,即使出于显而易见的原因,添加足够多的元素在某些时候必然会开始需要动态内存分配。

【讨论】:

  • 澄清一下,return Expression(...) 不应该像 C++11 中的右值,因为我没有将它分配给任何变量吗?
  • @AbhraBasak 这是一个右值,这就是移动构造函数可以工作的原因,但如果你没有移动构造函数,则不会。
  • 没关系,很抱歉我在长句中混淆了自己。 :)
  • hvd:你的意思是用户声明的构造函数吗?
  • @AbhraBasak 不,我的意思是析构函数。
【解决方案2】:

您的问题是您正在混合存在于自己范围内的临时对象和 unique_ptr 拥有的对象:

Expression Expression::operator+(Expression&& that) {
    return Expression(unique_ptr<Expression>(this),
                      unique_ptr<Expression>(&that),
                      unique_ptr<IBinaryOperator>(new SumCoNorm),
                      nullptr);
}

您想返回一个新的Expression 对象作为临时对象,并且您拥有现有对象(通过*this)并且您拥有that。您想获得所有权,但您不能,因此编译器会尝试创建一个副本。无论如何,临时对象都会被破坏,您无法阻止,因此您无法通过将指向它的指针放入 unique_ptr 来获取所有权。

你需要的是类似的东西

// Note: free function taking *two* operands
unique_ptr<Expression> operator+(unique_ptr<Expression> lhs,
                                 unique_ptr<Expression> rhs) {
    return unique_ptr<Expression>(
      new Expression(std::move(lhs),
                     std::move(rhs),
                     unique_ptr<IBinaryOperator>(new SumCoNorm),
                     nullptr));
}

并相应地处理最终结果。

【讨论】:

  • 这仍然需要一个可用的复制或移动构造函数,即使 RVO 将跳过调用它。按值返回总是如此。
  • 等等,我刚刚注意到您将返回类型更改为unique_ptr,这也是我会做的......但返回表达式不是指针。也许Expression 应该是make_unique&lt;Expression&gt; ?
  • @BenVoigt 当然,我只是忘了调整那部分。谢谢! (C++11 的固定答案,C++14 make_unique 是更好的选择)
  • 我正在创建的新Expression 完全负责this 和&amp;that 的所有权。我认为这种所有权授予是允许的,因为与this 关联的对象不是本地的,它在创建(a &amp; b) | (c &amp; ~d) 之类的表达式时存在。如果我错了,请纠正我。
  • @@DanielFrey 很棒的答案。但这无助于从生成的表达式中获得有用的意义,因为它会到处都有遵循运算符(如果在整个表达式之外使用,至少有一个)。我不仅关注正确性,还关注我提到的抽象和表达能力。
【解决方案3】:

第 1 步:Expression(Expression&amp;&amp;)=default。

第 2 步:unique_ptr&lt;Expression&gt;(this) 之类的子句变为 unique_ptr&lt;Expression&gt;(new Expression(std::move(*this))),unique_ptr&lt;Expression&gt;(&amp;that) 变为 unique_ptr&lt;Expression&gt;(new Expression(std::move(that)))。

现在,这有异常安全问题,所以你要写:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&...args) {
  return {new T(std::forward<Args>(args)...)};
}

这既让事情更安全,又让你这样做:

make_unique<Expression>(std::move(*this))
make_unique<Expression>(std::move(that))

更简单、更安全。

接下来,您几乎需要在任何地方区分Expression&amp; 和Expression&amp;&amp;。

学习表达式模板并使用该技术可能会更好。这将使您拥有看起来像 x + 5 或类似的表达式。

【讨论】:

    猜你喜欢
    • 2011-09-16
    • 1970-01-01
    • 2011-01-22
    • 2015-08-30
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    • 2011-11-29
    相关资源
    最近更新 更多