Перегрузка оператора присваивания в C++

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


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

    return *this;
}

Это - неконстанта, чтобы позволить функциям членства неконстанты быть названными в случаях как:


( a = b ).f();

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

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

16
задан jasonline 15 March 2010 в 14:26
поделиться

9 ответов

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

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

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

int &a = (some_int = 0); // works
17
ответ дан 30 November 2019 в 16:00
поделиться

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

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

4
ответ дан 30 November 2019 в 16:00
поделиться

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

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

2
ответ дан 30 November 2019 в 16:00
поделиться

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

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

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

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

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

3
ответ дан 30 November 2019 в 16:00
поделиться

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

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

16
ответ дан 30 November 2019 в 16:00
поделиться

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

Вы, конечно, можете придумать ситуацию, когда необходима ссылка, но на практике такие ситуации возникают редко - если вообще возникают.

1
ответ дан 30 November 2019 в 16:00
поделиться

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

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

1
ответ дан 30 November 2019 в 16:00
поделиться

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

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


поздняя правка:

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

a = b = c;

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

(a = b) = c;

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

(a = b) = c;

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

1
ответ дан 30 November 2019 в 16:00
поделиться

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

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

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

0
ответ дан 30 November 2019 в 16:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: