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


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

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

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

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

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

6 ответов:

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

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

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

немного разъяснений относительно того, почему предпочтительнее вернуться по ссылке для 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. Прочитайте другие сообщения, почему это может быть предпочтительнее)

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

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

int x, y, z;

x = y = z = 0;

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

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

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

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

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

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

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

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

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

там нет основного требования языка на тип результата определяемого пользователем 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;. Для небольшого класса, возвращающего a копирование может быть наиболее эффективным, в то время как для более крупного класса возврат по ссылке, как правило, будет наиболее эффективным (и копия, запретительно неэффективная).

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


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

C++11 §8.4.2 / 1:

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