Подтвердить что ты не робот

Не нужен ли здесь константный модификатор?

В "Эффективном С++" в пункте 3 говорится: "Используйте const, когда это возможно", и он дает пример вроде:

const Rational operator*(const Rational& lhs, 
                            const Rational& rhs);

чтобы клиенты не могли совершать такие зверства, как это:

Rational a, b, c;
...
(a * b) = c;   // invoke operator= on the result of a*b!

Но не является ли ссылочное возвращаемое значение функций allready a rvalue? Так зачем же это делать?

4b9b3361

Ответ 1

Дело в том, что для типов классов (но не для встроенных типов) a = b является сокращением до a.operator=(b), где operator = является функцией-членом. И функции-члены могут вызываться на r значениях, таких (a * b), созданных Rational::operator*. Чтобы применить аналогичную семантику, как для встроенных rvalues ​​( " сделать как ints do" ), некоторые авторы (включая Meyers) рекомендовали в С++ 98 для возврата по const-rvalue для классов с такими операторами.

Однако, в С++ 11 возвращение const-rvalue является плохой идеей, поскольку она будет препятствовать семантике перемещения, потому что константы rvalues ​​не могут привязываться к T&&.

В своих заметках Обзор нового С++ (С++ 11), Скотт Мейерс дает точно тот же пример из его старой книги, и приходит к выводу, что теперь считается плохой дизайн, чтобы добавить значение const. Рекомендованная подпись теперь

Rational operator*(const Rational& lhs, const Rational& rhs);

ОБНОВЛЕНИЕ: Как видно из комментариев @JohannesSchaub-litb, в С++ 11 вы также можете использовать спецификатор ссылок на оператор присваивания так, чтобы он принимал только lvalues ​​в качестве его левого аргумента (т.е. указатель *this, поэтому эта функция также известна как "ссылки rvalue для этого" ). Вам понадобится g++ >= 4.8.1 (только что выпущенный) или Clang >= 2.9, чтобы использовать его.

Ответ 2

Константный модификатор возвращаемого значения не является необходимым и может препятствовать семантике перемещения. Предпочтительным способом предотвращения присвоения rvalues ​​в С++ 11 является использование ref-qualifiers.

struct Rational
{
  Rational & operator=( Rational other ) &; // can only be called on lvalues
};

Rational operator*( Rational const & lhs, Rational const & rhs );

Rational a, b, c;

(a * b) = c; // error

Ответ 3

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

Rational operator*(const Rational& lhs, const Rational& rhs);

(Разумеется, const параметры являются хорошей практикой, а наличие постоянных ссылочных параметров еще лучше, так как это означает, что компилятор не будет брать глубокие копии. Но в этом случае не обязательно иметь возвращаемое значение ссылки так как вы получите зависающую ссылку, которая является катастрофической. Но обратите внимание, что иногда, принимая ссылку медленнее, чем пропуск по значению. Я думаю, что double и int входят в эту категорию на многих платформах.)

Ответ 4

Потому что вы можете написать (a * b) == c вместо i.e.

if ((a * b) = (c + d)) // executes if c+d is true

Но вы хотели

if ((a * b) == (c + d)) // executes if they're equal

Ответ 5

Я предполагаю, что вы хотели бы сделать в соответствии с вашим вопросом: объявить соответствующий оператор = private, чтобы он больше не был доступен.

поэтому вы хотите перегрузить подпись, которая соответствует (a*b) = c. Я согласен с тем, что левая часть является выражением, и поэтому значение r будет лучше. однако вы игнорируете тот факт, что это возвращаемое значение функции, если вы перегружаете функцию, чтобы вернуть значение r, которое компилятор будет жаловаться на недопустимую перегрузку, поскольку правила перегрузки не учитывают возвращаемые значения.

Как указано здесь, перегрузка оператора для назначения всегда является внутренним определением класса. если бы была нечетная подпись типа void operator=(foo assignee, const foo& assigner);, разрешение перегрузки могло бы соответствовать первой части как rvalue (тогда вы могли бы удалить ее или объявить ее закрытой).

Итак, вы можете выбрать один из двух миров:

  • жить с тем, что пользователи могут писать глупые вещи, такие как (a*b) = c, что не так, но сохраняет значение c в недоступном временном
  • используйте подпись const foo operator*(const foo& lhs, const foo& rhs), которая не разрешает семантику (a*b) = c и жертву перемещения

код

#include <utility>

class foo {
    public:
    foo() = default;
    foo(const foo& f) = default;
    foo operator*(const foo& rhs) const {
        foo tmp;
        return std::move(tmp);
    }
    foo operator=(const foo& op) const {
        return op;
    }
    private:
    // doesn't compile because overloading doesn't consider return values.
    // conflicts with foo operator=(const foo& op) const;
    foo && operator=(const foo& op) const; 
};


int main ( int argc, char **argv ) {
    foo t2,t1;
    foo t3 = t2*t1;
    foo t4;
    (t2 * t1) = t4;
    return 0;
}