Почему оператор копирования должен возвращать ссылку / постоянную ссылку? - PullRequest
59 голосов
/ 24 июня 2010

В C ++ концепция возврата ссылки из оператора присваивания копии мне неясна.Почему оператор присваивания копии не может вернуть копию нового объекта?Кроме того, если у меня есть класс A и следующее:

A a1(param);
A a2 = a1;
A a3;

a3 = a2; //<--- this is the problematic line

operator= определяется следующим образом:

A A::operator=(const A& a)
{
    if (this == &a)
    {
        return *this;
    }
    param = a.param;
    return *this;
}

Ответы [ 6 ]

64 голосов
/ 24 июня 2010

Строго говоря, результат оператора назначения копирования не должен возвращать ссылку, хотя для имитации поведения по умолчанию, используемого компилятором C ++, он должен возвращать неконстантную ссылку на объект, который назначен ( неявно сгенерированный оператор присваивания копии вернет неконстантную ссылку - C ++ 03: 12.8 / 10). Я видел довольно много кода, который возвращает void от перегрузок при назначении копирования, и я не могу вспомнить, когда это вызвало серьезную проблему. Возвращение void предотвратит пользователей от «цепочки назначений» (a = b = c;) и предотвратит использование результата назначения, например, в тестовом выражении. Хотя такой код ни в коем случае не является неслыханным, я также не думаю, что он особенно распространен - ​​особенно для не примитивных типов (если интерфейс для класса не предназначен для таких тестов, таких как iostreams). *

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

Эти другие вопросы SO связаны (вероятно, не совсем обманчиво), в которых есть информация / мнения, которые могут вас заинтересовать.

53 голосов
/ 23 января 2011

Немного пояснений, почему предпочтительно возвращать по ссылке для operator=, а не возвращать по значению - поскольку цепочка a = b = c будет работать нормально, если возвращается значение.

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

Однако, если вы вернетесь по значению для operator=, вы будете вызывать конструктор И деструктор КАЖДЫЙ раз, когда вызывается оператор присваивания !!

Итак, учитывая:

A& operator=(const A& rhs) { /* ... */ };

Тогда

a = b = c; // calls assignment operator above twice. Nice and simple.

Но

A operator=(const A& rhs) { /* ... */ };

a = b = c; // calls assignment operator twice, calls copy constructor twice, calls destructor type to delete the temporary values! Very wasteful and nothing gained!

В итоге, возвращение ценностью ничего не дает, но многое можно потерять.

( Примечание : Это не означает, что оператор присваивания возвращает lvalue. Прочитайте другие сообщения, чтобы узнать, почему это может быть предпочтительнее)

8 голосов
/ 18 ноября 2014

Когда вы перегружаете operator=, вы можете записать его, чтобы вернуть любой тип, который вы хотите.Если вы хотите достаточно сильно, вы можете перегрузить X::operator=, чтобы вернуть (например) экземпляр какого-то совершенно другого класса Y или Z.Как правило, это крайне нецелесообразно.

В частности, вы обычно хотите поддерживать цепочку operator=, как это делает C.Например:

int x, y, z;

x = y = z = 0;

В этом случае вы обычно хотите вернуть lvalue или rvalue типа, которому назначен.Это оставляет только вопрос о том, возвращать ли ссылку на X, постоянную ссылку на X или X (по значению).

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

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

Возврат значения вместо ссылки имеет как теоретические, так и практические проблемы.С теоретической точки зрения, у вас есть основное разъединение между = обычно означает и что это означает в этом случае.В частности, когда назначение обычно означает «взять этот существующий источник и присвоить его значение этому существующему месту назначения», оно начинает означать нечто более похожее на «взять этот существующий источник, создать его копию и присвоить это значение этому существующему месту назначения»."

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

Единственным другим вариантом (добавленным в C ++ 11) будет возвращение ссылки на rvalue.,Это может легко привести к неожиданным результатам - цепочечное присваивание, такое как a=b=c;, может уничтожить содержимое b и / или c, что будет довольно неожиданным.

Это оставляет возврат нормальной ссылки (не ссылка на const или ссылка на rvalue) как единственный вариант, который (разумно) надежно производит то, что обычно хочет большинство людей.

5 голосов
/ 24 июня 2010

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

4 голосов
/ 24 июня 2010

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

3 голосов
/ 18 ноября 2014

Базового языка в типе результата пользовательского operator= не требуется, но в стандартной библиотеке есть такое требование:

C ++ 98 §23.1 / 3:

& rdquo; Тип объектов, хранящихся в этих компонентах, должен соответствовать требованиям CopyConstructible типы (20.1.3) и дополнительные требования для типов Assignable.

C ++ 98 §23.1 / 4:

& rdquo; В таблице 64 T - это тип, используемый для создания экземпляра контейнера, t - это значение T, а u - это значение (возможно, * 1023). *) T.

enter image description here


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

До тех пор, пока я не узнал о требованиях стандартной библиотеки, я обычно разрешал operator= возвращать void для эффективности и во избежание абсурда поддержки плохого кода на основе побочных эффектов.


В C ++ 11 дополнительно требуется T& тип результата для default -ing оператора присваивания, потому что

C ++ 11 §8.4.2 / 1:

& rdquo; Функция с явным значением по умолчанию должна иметь [& hellip;] тот же объявленный тип функции (за исключением, возможно, отличающихся ref-qualifiers и за исключением того, что в В случае конструктора копирования или оператора назначения копирования тип параметра может быть «ссылкой на неконстантный T», где T - имя класса функции-члена), как если бы он был неявно объявлен

...