Перегрузка оператора присваивания в С++ - программирование
Подтвердить что ты не робот

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

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


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

    return *this;
}

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


( a = b ).f();

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

Причина f() может изменить a. (мы возвращаем неконстантную ссылку)

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

Ответ 4

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

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

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

Теперь предположим, что вы используете старую версию С++, где присваивание возвращало значение rvalue. В этом случае (*this=other); даст это временное значение. Затем вы привязываете ссылку на временную, уничтожая временную и, наконец, возвращаете оборванную ссылку на разрушенную временную.

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

Ответ 5

Если ваш оператор присваивания не принимает параметр ссылки 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() мутирует объект, он не мутирует временный.

Ответ 6

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

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

Ответ 7

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

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


позднее редактирование:

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

a = b = c;

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

(a = b) = c;

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

(a = b) = c;

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

Ответ 8

Это пункт 10 отличной книги Скотта Мейерса, Эффективный С++. Возвращение ссылки из operator= - это только соглашение, но оно хорошее.

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

Ответ 9

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

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

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

Ответ 10

Возврат по ссылке сокращает время выполнения цепочки операций. E. g.

a = b = c = d;

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

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

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

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

  • Вызывается оператор присваивания копий. c становится равным d, возвращается ссылка на объект lvalue
  • То же самое. b становится равным c, возвращается ссылка на объект lvalue
  • То же самое. a становится равным b, возвращается ссылка на объект lvalue

В целом: вызывается только три оператора копирования и вообще нет ctors!

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

P.S.: Оператор присваивания копии: operator=(const Class& rhs).

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