Перегрузка оператора присваивания в C++

Как я понимаю, при перегрузке operator= возвращаемое значение должно быть ссылкой non-const.


A& A::operator=( const A& )
{
    // check for self-assignment, do assignment

    return *this;
}

это не-const, чтобы разрешить функции-члены non-const вызываться в таких случаях, как:


( a = b ).f();

но почему он должен возвращать ссылку? В каком случае это даст проблему, если возвращаемое значение не объявлено ссылкой, скажем, return by value?

предполагается, что конструктор копирования реализован правильно.

10 ответов


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

a = b; // huh, why does this create an unnecessary copy?

кроме того, это было бы удивительно для пользователей вашего класса, так как встроенный оператор присваивания не копирует аналогично

int &a = (some_int = 0); // works

хороший общий совет при перегрузке операторов- "делать, как делают примитивные типы", и поведение по умолчанию присваивания примитивному типу таково.

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


причина f () изменить a. (мы возвращаем ссылку non-const)

Если мы вернем значение (копию)a, f () изменить копию, а не a


Я не уверен, как часто вы хотели бы это сделать, но что-то вроде: (a=b)=c; требуется ссылка на работу.

Edit: хорошо, есть немного больше, чем это. Большая часть рассуждений полу-исторический. Существует больше причин, по которым вы не хотите возвращать rvalue, чем просто избегать ненужной копии во временный объект. Используя (незначительную) вариацию на примере, первоначально опубликованном Andrew Koenig, рассмотрим что-то вроде этого:

struct Foo { 
    Foo const &assign(Foo const &other) { 
        return (*this = other);
    }
};

Теперь предположим, что вы используете старая версия C++, где присваивание вернуло rvalue. В таком случае ...--2--> даст это временное. Затем вы связываете ссылку на временное, уничтожаете временное и, наконец, возвращаете висящую ссылку на разрушенное временное.

правила, которые были приняты с тех пор (продление срока службы временного, используемого для инициализации ссылки), по крайней мере смягчили бы (и могли бы полностью вылечить) эту проблему, но я сомневаюсь, что кто-то повторно посетил эту конкретную ситуацию после того, как эти правила были написаны. Это было немного похоже на уродливый драйвер устройства, который включает в себя kludges для работы с десятками ошибок в разных версиях и вариациях оборудования - его, вероятно, можно было бы рефакторировать и упростить, но никто не уверен, когда какое-то кажущееся безобидным изменение сломает что-то, что в настоящее время работает, и в конечном итоге никто не хочет даже смотреть на это, если они могут помочь.


если оператор присваивания не принимает параметр ссылки const:

A& A::operator=(A&); // unusual, but std::auto_ptr does this for example.

или если класс A имеет изменяемые члены (счетчик ссылок?), то возможно, что оператор присваивания изменяет объект, назначаемый из, а также назначенный. Если у вас есть такой код:

a = b = c;

на b = c назначение произойдет первым и вернет копию (назовите это b') по значению вместо возврата ссылки на b. Когда a = b' назначение сделано, мутирующий оператор присваивания изменит b' копировать вместо реального b.

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

если вы собираетесь сделать что-то вроде (a = b).f() тогда вы захотите, чтобы он вернулся по ссылке, чтобы если f() мутирует объект, это не мутация временная.


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

вы, очевидно, можете придумать ситуацию, когда ссылка необходима, но они редко-если вообще-появляются на практике.


Это пункт 10 превосходной книги Скотта Мейерса, Эффективный C++. Возврат ссылки из operator= Это только конвенция, но она хорошая.

Это только соглашение; код, который не следует за ним, будет компилироваться. Однако за соглашением следуют все встроенные типы, а также все типы в стандартной библиотеке. Если у вас нет веской причины поступать по-другому, не делайте этого.--4-->


если вы беспокоитесь, что возвращение неправильной вещи может тихо вызвать непреднамеренные побочные эффекты, вы можете написать свой operator=() вернуться void. Я видел довольно много кода, который делает это (я предполагаю, из лени или просто не зная, каким должен быть тип возврата, а не для "безопасности"), и это вызывает несколько проблем. Вид выражений, которые должны использовать ссылку, обычно возвращаемую operator=() довольно редко используются, и почти всегда легкий код является альтернативой.

я не уверен, что поддержу возвращение void (в обзоре кода я бы, вероятно, вызвал его как то, что вы не должны делать), но я бросаю его там в качестве опции, чтобы рассмотреть, если вы хотите не беспокоиться о том, как странные использования оператора присваивания могут быть обработаны.


поздняя правка:

кроме того, я должен был изначально упомянул, что вы можете разделить разницу, имея свой operator=() возвратить const& - это все равно позволит назначение цепочки:

a = b = c;

но запретит некоторые из более необычных применений:

(a = b) = c;

обратите внимание, что это делает оператор присваивания семантикой, подобной тому, что он имеет в C, где значение, возвращаемое = оператор не является lvalue. В C++ стандарт изменил его так, что = оператор возвращает тип левого операнда, поэтому это lvalue, но, как отметил Стив Джессоп в комментарии к другому ответу, в то время как это делает его компилятором будет принимать

(a = b) = c;

даже для встроенных модулей результатом является неопределенное поведение для встроенных модулей с a модифицируется дважды без промежуточной точки последовательности. Эта проблема избегается для non-builtins с operator=() - за operator=() вызов функции является точкой последовательности.


Если он вернул копию, вам потребуется реализовать конструктор копирования почти для всех нетривиальных объектов.

также это вызовет проблемы, если вы объявите конструктор копирования частным, но оставите оператор присваивания открытым... вы получите ошибку компиляции, если вы попытаетесь использовать оператор присваивания вне класса или его экземпляров.

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


возврат по ссылке сокращает время выполнения цепных операций. Е. Г. :

a = b = c = d;

давайте посмотрим действия, которые будут называться, если operator= возвращает значение.

  1. копировать назначение opertor= for c делает c равна d а затем создает временное аноним объект (вызывает copy ctor). Назовем это tc.
  2. тогда оператор= for b называется. Правый боковой объект-tc. Двигаться вызывается оператор присваивания. b будет равна tc. А затем функция копирует b для временного анонима, назовем его tb.
  3. то же самое повторилось, a.operator= возвращает временную копию a. После оператора ; все три временных объекта уничтожены

всего: 3 copy ctors, 2 оператора перемещения, 1 оператор копирования

давайте посмотрим, что изменится, если operator= вернет значение ссылка:

  1. вызывается оператор назначения копирования. c будет равна d, ссылка на объект lvalue возвращается
  2. то же самое. b будет равна c, ссылка на объект lvalue возвращается
  3. то же самое. a будет равна b, ссылка на объект lvalue возвращается

всего: вызывается только три оператора копирования и нет ctors на все!

я рекомендую вам вернуть значение по ссылке const, это не позволит вам написать сложный и ненавязчивый код. С более чистым кодом найти ошибки будет намного проще:)( a = b ).f(); лучше разделить на две строки a=b; a.f();.

П. С.: Копирующий оператор присваивания : operator=(const Class& rhs).

двигаться оператора присваивания : operator=(Class&& rhs).