У меня рекурсивный класс 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&)'
в каждом из перегруженных операторов (в операторах возврата).
Я чувствую, что какая-то функция внутренне пытается использовать конструктор копирования unique_ptr, который не существует. Я делаю что-то не так с перемещением указателей здесь и там? Я использую C ++ 11 с GCCv4.8.
Предложения по изменению интерфейса класса любым способом приветствуются. Я бы предпочел избегать использования сырых указателей.
Заметка: Пожалуйста, не предлагайте использовать генератор синтаксического анализатора или тому подобное, например Boost.Spirit, YARD или YACC. Приложение требует от меня, чтобы реализовать это с нуля.
Концептуально,
return Expression(...);
создает новый Expression
объект, а затем копирует или перемещает его в возвращаемое значение. В вашем случае у вас нет конструктора перемещения (не существует неявного конструктора перемещения, поскольку у вас есть деструктор, объявленный пользователем) и конструктор удаленных копий, так что это невозможно.
Ты можешь использовать
return {...};
чтобы избежать операции копирования / перемещения, или вы можете убедиться, что у вас есть конструктор перемещения:
class Expression {
public:
Expression() = default;
Expression(Expression &&) = default;
...
};
Дополнительное примечание после комментариев от Бена Фойгта, который справедливо указывает, что это делает его компиляцией, но на самом деле не работает:
unique_ptr
работает только для объектов, выделенных с new
, если вы не используете пользовательский удалитель. В вашем случае это просто не сработает, вам нужно переосмыслить свою логику.
я считать ты должен держать unique_ptr
только как деталь реализации, и не имеют внешних вызывающих абонентов беспокоиться об этом. Если вы убедитесь, что ваш Expression
является копируемым и подвижным, у вас не должно быть проблем с динамическим размещением объектов по мере необходимости изнутри Expression
хранить как _lhs
а также _rhs
, принимать std::vector<T>
в качестве примера, где вам не нужно new
чтобы использовать его, хотя по очевидным причинам добавление достаточного количества элементов в определенный момент обязательно начнет требовать динамического выделения памяти.
Ваша проблема в том, что вы смешиваете временные объекты, которые живут в своей области видимости, и объекты, принадлежащие 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));
}
и обработать окончательный результат соответственно.
Шаг 1: Expression(Expression&&)=default
,
Шаг 2: пункты вроде unique_ptr<Expression>(this)
становиться unique_ptr<Expression>(new Expression(std::move(*this)))
, а также unique_ptr<Expression>(&that)
становится unique_ptr<Expression>(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&
а также Expression&&
почти везде
Возможно, вам лучше узнать шаблоны выражений и использовать эту технику. Это позволит вам иметь выражения, которые выглядят как x + 5
или т.п.