Перегрузка оператора присваивания в C ++ - PullRequest
15 голосов
/ 15 марта 2010

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


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

    return *this;
}

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


( a = b ).f();

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

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

Ответы [ 10 ]

16 голосов
/ 15 марта 2010

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

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

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

int &a = (some_int = 0); // works
14 голосов
/ 15 марта 2010

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

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

4 голосов
/ 15 марта 2010

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

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

3 голосов
/ 15 марта 2010

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

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

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

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

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

2 голосов
/ 15 марта 2010

Если ваш оператор присваивания не принимает постоянный опорный параметр:

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() мутировал объект, он не мутировал временно.

1 голос
/ 15 марта 2010

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

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

1 голос
/ 15 марта 2010

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

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


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

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

a = b = c;

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

(a = b) = c;

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

(a = b) = c;

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

1 голос
/ 15 марта 2010

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

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

0 голосов
/ 27 апреля 2016

Возврат по ссылке сокращает время выполнения цепных операций. Например :

a = b = c = d;

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

  1. Копировать присваивание opertor = for c делает c равным d, а затем создает временный анонимный объект (вызывает копию ctor). Давайте назовем это tc.
  2. Затем вызывается оператор = для b. Правосторонний объект tc. Оператор назначения перемещения называется. b становится равным tc. А затем функция копирует b во временный аноним, назовем его tb.
  3. То же самое происходит снова, a.operator= возвращает временную копию a. После оператора ; все три временных объекта уничтожаются

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

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

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

Всего: только три оператора копирования вызываются, а кортов вообще нет!

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

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

Оператор назначения перемещения: operator=(Class&& rhs).

0 голосов
/ 15 марта 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...