Почему оператор присваивания копий возвращает ссылку reference/const? - программирование
Подтвердить что ты не робот

Почему оператор присваивания копий возвращает ссылку reference/const?

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

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

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

Однако, если вы вернетесь по значению для operator =, вы вызовете конструктор AND destructor. Через какое-то время вызывается оператор присваивания!!

Итак, дано:

A& operator=(constA& rhs){ ... };

Тогда

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

но

A operator=(constA& 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!

В общем, нет ничего, что можно было бы вернуть по стоимости, но многое потерять.

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

Ответ 3

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

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

int x, y, z;

x = y = z = 0;

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

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

С++ 98 §23.1/3:

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

С++ 98 §23.1/4:

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

enter image description here


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

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


С С++ 11 дополнительно требуется тип результата T& для default - для оператора присваивания, поскольку

С++ 11 §8.4.2/1:

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