47

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

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; 
} 
+3

Там нет такого требования. Но если вы хотите придерживаться принципа наименьшего удивления, вы вернете 'A &' так же, как 'a = b' - выражение lvalue, относящееся к' a', в случае 'a' и' b' - ints. – sellibitze

+0

@MattMcNabb Спасибо, что сообщили мне! Сделаю это – bks

+0

Почему мы не можем вернуть 'A *' из оператора присваивания копий? Я предполагаю, что назначение цепочки будет работать правильно. Может ли кто-нибудь помочь понять опасности возврата 'A *', если они есть. –

ответ

54

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

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

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

+0

Мне было полезно вернуть пустоту в оператор присваивания, когда мне нужно было предотвратить автоматическое уничтожение объектов по мере их поступления из стека , Для подсчитанных объектов вы не хотите, чтобы деструкторы вызывались, когда вы не знаете о них. – cjcurrie

+0

+1 один из двух правильных ответов. –

4

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

+0

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

+4

Вы бы получили (a = (b = c)), который, по-моему, все равно произведет намеченный результат. Только если вы сделали (a = b) = c, он был бы сломан. – Puppy

4

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

+0

Это была бы хромая (и необычная) реализация copy-ctor. В большинстве случаев причина возврата 'A &' из 'A :: operator =' отличается. – jpalecek

+0

@jpalecek, я согласен, но, учитывая исходное сообщение и отсутствие ясности при постановке фактической проблемы, наиболее вероятно, что оператор присваивания выполняет результат в stackoverflow из-за бесконечной рекурсии. Если есть еще одно объяснение этого вопроса, я хотел бы это узнать. – MSN

+0

@MSN Я не знаю, что это была его проблема или нет. Но, безусловно, ваш пост здесь затронул мою проблему +1 для этого – Invictus

43

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

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

Однако, если вы возвращаете по значению для оператора =, вы будете называть конструктор и деструктор каждый раз, когда оператор присваивания называется !!

Так Дано:

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! 

В общем, нет ничего получил, вернувшись по значению, но много потерять.

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

+0

Очень хороший ответ. Хотелось бы, чтобы я мог дать +10 баллов этому ответу. Отлично. Так держать !!! – Destructor

8

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

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

int x, y, z; 

x = y = z = 0; 

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

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

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

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

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

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

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

+0

** + 1 ** Не уверен в обсуждении ссылки, но +1 для «вы можете». –

+0

Я не вижу, какую опасную ситуацию вы упоминали в части «Возвращение константной ссылки»; если кто-то пишет 'const T & ref = T {} = t;', то это обвисшая ссылка независимо от того, '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' По иронии судьбы, это нормально, если 'operator =' возвращается по значению! –

+0

@MattMcNabb: Упс - это должно было сказать 'lvalue reference'. Спасибо, что указали это (потому что да, ссылка rvalue * явно * плохая идея здесь). –

3

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

C++ 98 §23.1/3:

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

C++ 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=void, для обеспечения эффективности и во избежание абсурдности поддержки плохого кода, основанного на побочном эффекте.


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

11 §8.4 C++.2/1:

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

Смежные вопросы