4

У меня есть рекурсивный класс Expression, обозначающие булевы подобные выражения, например:C++ оператора перегрузки в рекурсивных выражениях классов

(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&)'

в каждом перегруженных операторов (в операторах return). Я чувствую, что какая-то функция внутренне пытается использовать конструктор копирования unique_ptr, которого не существует. Я что-то делаю с движущимися указателями здесь и там? Я использую C++ 11 с GCCv4.8.

Предложения по изменениям в интерфейсе классов приветствуются. Я бы предпочел избегать использования исходных указателей.

Примечание: Пожалуйста, не предлагайте использовать генератор парсера или подобное, например Boost.Spirit, YARD или YACC. Приложение требует от меня реализовать это с нуля.

+0

Я признаю, что я ржавый, но мне кажется странным, что вы перемещаете аргументы своего конструктора в члены данных объекта. – Richard

+0

Nevermind; теперь я вижу, что это локальные 'unique_ptr <>. Я ожидал, что они будут ссылками. – Richard

ответ

3

Концептуально

return Expression(...); 

создает новый объект Expression, а затем копирует или перемещает его к возвращаемому значению. В вашем случае у вас нет конструктора перемещения (нет конструктора с неявным движением, так как у вас есть объявленный пользователем деструктор) и удаленный экземпляр копии, поэтому это невозможно.

Вы можете использовать

return {...}; 

, чтобы избежать операции копирования/перемещения, или вы можете убедиться, что у вас есть перемещение конструктор:

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

Дополнительное примечание, после комментариев Бена Фогта, который по праву указывает, что это заставляет его скомпилировать, но фактически не работает:

unique_ptr работает только для объектов, выделенных с помощью new, за исключением лет u используйте пользовательский дебетер. В вашем случае это просто не сработает, вам нужно будет переосмыслить свою логику.

I думаю, вы должны сохранить unique_ptr только как деталь реализации, и не должны беспокоиться о внешних абонентах. Если вы гарантируете, что ваш Expression можно копировать и переносить, у вас не должно быть проблем с динамическим распределением объектов по мере необходимости с Expression для хранения как _lhs и _rhs.Возьмите std::vector<T> в качестве примера, где вам не нужно new, чтобы использовать его, хотя по очевидным причинам добавление достаточного количества элементов в какой-то момент обязательно начнет требовать распределения динамической памяти.

+0

Просто уточнение, не 'return Expression (...)' Предполагается, что он должен действовать как rvalue в C++ 11, так как я не назначал его какой-либо переменной? –

+0

@AbhraBasak Это значение rvalue, поэтому конструктор перемещения будет работать, но не если у вас нет этого конструктора перемещения. – hvd

+0

Ничего, извините, я смутился в длинном предложении. :) –

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

и обрабатывать конечный результат соответственно.

+0

Это все еще требует, чтобы конструктор копирования или перемещения был доступен, даже если RVO пропустит его вызов. Возвращаемое значение всегда делает. –

+0

Подождите, я просто заметил, что вы изменили тип возвращаемого значения на 'unique_ptr', что я и сделаю ... но выражение return не является указателем. Возможно, 'Expression' должен быть' make_unique '? –

+0

@BenVoigt Конечно, я просто забыл адаптировать эту часть. Благодаря! (Исправленный ответ для C++ 11, с C++ 14 «make_unique» - лучший вариант). –

1

Этап 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 или тому подобное.

Смежные вопросы