4

I have a recursive class Expression which denotes boolean-like expressions, for instance:

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

Note that Expression takes care of both unary and binary expressions. Basically, Expression should follow the CFG similar to a boolean expression.

I have designed the class in this way:

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;
};

The implementation of the constructor and one each of the binary and unary operators are shown below:

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));
}

The class fails to compile with

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

in each of the overloaded operators (in the return statements). I get the feel that some function is internally trying to use the copy constructor of unique_ptr, which does not exist. Am I doing something wrong with moving pointers here and there? I am using C++11 with GCCv4.8.

Suggestions to changes in class interface in any way is welcome. I would prefer avoiding the use of raw pointers.

Note: Please do not suggest using a parser generator or the like, such as Boost.Spirit, YARD or YACC. The application requires me to implement this from scratch.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
Abhra Basak
  • 382
  • 4
  • 13
  • I'll admit to being rusty, but it seems strange to me that you are moving your constructor's arguments into the object's data members. – Richard Jan 20 '14 at 15:42
  • Nevermind; now I see that these are local `unique_ptr<>`s. I expected them to be references. – Richard Jan 20 '14 at 15:43

3 Answers3

3

Conceptually,

return Expression(...);

creates a new Expression object, and then copies or moves it to the return value. In your case, you have no move constructor (there is no implicit move constructor since you have a user-declared destructor), and a deleted copy constructor, so this is not possible.

You can use

return {...};

to avoid the copy/move operation, or you can make sure you have a move constructor:

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

Additional note, after comments from Ben Voigt who rightly points out that this makes it compile, but not actually work:

unique_ptr only works for objects allocated with new, unless you use a custom deleter. In your case, it just won't work, you'll need to re-think your logic.

I think you should keep unique_ptr only as an implementation detail, and not have outside callers worry about it. If you ensure your Expression is copyable and movable, you should have no problems dynamically allocating objects as needed from within Expression to store as _lhs and _rhs. Take std::vector<T> as an example, where you don't need new to use it, even though for obvious reasons, adding enough elements will at some point necessarily start requiring dynamic memory allocations.

  • Just a clarification, isn't `return Expression(...)` supposed to act like an rvalue in C++11, since I didn't assign it to any variable? – Abhra Basak Jan 20 '14 at 15:55
  • @AbhraBasak It is an rvalue, which is why a move constructor would work, but not if you don't have that move constructor. –  Jan 20 '14 at 15:56
  • Nevermind, I'm sorry I confused myself in the long sentence. :) – Abhra Basak Jan 20 '14 at 15:59
  • hvd: Did you mean a user-declared constructor? – Abhra Basak Jan 20 '14 at 16:00
  • 1
    @AbhraBasak No, I did mean destructor. –  Jan 20 '14 at 16:01
  • He needs a move constructor to enable return by value. – Ben Voigt Jan 20 '14 at 16:12
  • Hmm, this answer is explanatory of what's going on, but it doesn't address the serious issue of trying to create `unique_ptr` long after the object has been created (and probably from objects with automatic lifetime). – Ben Voigt Jan 20 '14 at 16:15
  • @BenVoigt You're right, I didn't. I think that would be useful info to add if I got it right, but getting that right is not that simple: Daniel Frey's answer is not sufficient to make the result work in a way that would make sense to me (although it is a good answer nonetheless). –  Jan 20 '14 at 16:47
  • Well, I think the issue merits at least a note **You can't use `unique_ptr` that way, `unique_ptr` can only control objects created on the stack with `new`.** – Ben Voigt Jan 20 '14 at 16:50
  • @Ben I see your point, but then again what I need as a result is an Expression (not a pointer), so that I can use it in another recombination. – Abhra Basak Jan 20 '14 at 17:00
  • @AbhraBasak: Except for this: If your result is an Expression, you **cannot** use it in another combination, because return values do not live past the full-expression. Putting the address of a temporary into a `unique_ptr` is an error. The `unique_ptr` will try to call `delete` on it, but you cannot delete something that did not come from `new`. This whole design is badly broken, and requires a lot more thinking about object lifetime. – Ben Voigt Jan 20 '14 at 17:13
  • @BenVoigt Having thought about it, I personally think `unique_ptr` should be kept as an implementation detail, `Expression` should handle required allocations internally, so that the caller does not have to worry about it. I've added a note. If you have a better suggestion I'll be happy to amend my answer, though. –  Jan 20 '14 at 17:27
2

Your problem is that you are mixing temporary objects which live in their own scope and objects owned by a unique_ptr:

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

You want to return a new Expression object as a temporary and you have the existing object (through *this) and you have that. You want to take ownership but you can't and hence the compiler tries to create a copy. The temporary would be destructed anyways and you can't prevent that, so you can't take ownership by putting a pointer to it into a unique_ptr.

What you would need is something like

// 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));
}

and handle the final result accordingly.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • That still requires a copy or move constructor to be available, even if RVO will skip calling it. Return-by-value always does. – Ben Voigt Jan 20 '14 at 16:12
  • Wait, I just noticed you changed the return type to a `unique_ptr`, which is what I would do as well.... but the return expression isn't a pointer. Perhaps `Expression` should be `make_unique` ? – Ben Voigt Jan 20 '14 at 16:14
  • @BenVoigt Of course, I just forgot to adapt that part. Thanks! (Fixed answer for C++11, with C++14 `make_unique` is the better option) – Daniel Frey Jan 20 '14 at 16:35
  • The new `Expression` that I am creating is solely responsible for the ownership of `this` and `&that`. I think that kind of ownership grant is allowed, because the object associated with `this` is not local, it exists when the expression like `(a & b) | (c & ~d)` is created. Please correct me if I am wrong. – Abhra Basak Jan 20 '14 at 16:48
  • @@DanielFrey Awesome answer. But this does not help getting useful sense out of the resulting expression, because it will be having deferencing operators all over (at least one if used outside the whole expression). I am not only looking at correctness, but also at abstraction and expressive ability like i mentioned. – Abhra Basak Jan 20 '14 at 16:59
  • @AbhraBasak You are wrong. If you want to have an object which is owned by a `unique_ptr` is **must** be allocated with `new` (indirectly through something like `make_unique` or directly). The returned object in your code (of type `Expression`) simply can not be owned (and later destructed) by a `unique_ptr`. – Daniel Frey Jan 20 '14 at 17:02
  • @AbhraBasak Even if it seems to work, it's illegal. I'd suggest to redesign your classes but the right design choices depend on a lot of details about the use-cases and what you want to optimize for, so I'm afraid there is no easy answer. – Daniel Frey Jan 20 '14 at 17:19
  • @Daniel What would be expected to happen at the time of its destruction? I mean, is there any effect of `delete this;`, given that it is a stack allocated variable? Anyways, the child variables are NOT meant to be used after the tree is created (only Evaluate on the root is what is desired, and to be specific, there are no child variables per se), so does this negatively affect the result? – Abhra Basak Jan 20 '14 at 17:22
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/45658/discussion-between-daniel-frey-and-abhra-basak) – Daniel Frey Jan 20 '14 at 17:26
1

Step 1: Expression(Expression&&)=default.

Step 2: Clauses like unique_ptr<Expression>(this) become unique_ptr<Expression>(new Expression(std::move(*this))), and unique_ptr<Expression>(&that) becomes unique_ptr<Expression>(new Expression(std::move(that))).

Now, this has exceptions safety issues, so you'll want write:

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

which both makes things safer, and lets you do this:

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

which is simpler and safer.

Next, you need to distinguish between Expression& and Expression&& pretty much everywhere.

You might be better off learning about expression templates, and using that technique. This would let you have expressions that look like x + 5 or the like.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524