Существуют ли ситуации, когда самостоятельное назначение полезно? - PullRequest
24 голосов
/ 26 мая 2019

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

Foo& operator=(const Foo& other)
{
  if (&other == this)
     return *this;
  ... // Do copy
}

По каким причинам вы не включили автоматическую защиту автоматически? Существуют ли случаи использования, когда самостоятельное назначение делает что-то нетривиальное и практически полезное?

Foo& operator=(const Foo& other)
{
  if (&other == this)
  {
    // Do something non-trivial
  }
  else
  {
    // Do copy
  }
  return *this;
}

Подведем итоги и обсудим к настоящему моменту

Похоже, нетривиальное самостоятельное назначение никогда не может быть действительно полезным. Единственный предложенный вариант заключался в том, чтобы поместить туда assert, чтобы обнаружить некоторые логические ошибки. Но есть вполне законные случаи самостоятельного назначения, такие как a = std::min(a, b), поэтому даже эта опция весьма сомнительна.

Но есть две возможные реализации тривиального самостоятельного назначения:

  1. Ничего не делать, если &other == this. Всегда работайте, хотя может иметь негативное влияние на производительность из-за дополнительного ветвления Но в определяемом пользователем операторе присваивания тест должен выполняться почти всегда явно.
  2. Скопируйте каждого члена в себя. Это то, что сделано по умолчанию. Если члены также используют операторы назначения по умолчанию, это может быть быстрее, потому что не требует дополнительного ветвления.

Я до сих пор не понимаю, почему стандарт C ++ не может гарантировать это в пользовательском операторе присваивания &other != this. Если вы не хотите разветвления, используйте оператор по умолчанию. Если вы переопределяете оператора, в любом случае требуется некоторый тест ...

Ответы [ 3 ]

19 голосов
/ 26 мая 2019

Защита самоназначения необходима только для типов, где пропускаемый код опасен при применении к самому себе. Рассмотрим случай, когда у вас есть пользовательский оператор присваивания, потому что у каждого отдельного объекта есть какой-то идентификатор, который вы не хотите копировать. Ну, вы можете просто "скопировать" другие значения в случаях самопредставления. Поэтому вставка невидимого теста для самостоятельного назначения - это просто добавление бессмысленной и потенциально дорогостоящей условной ветви.

Так что не самоопределение полезно; речь идет о самопредставлении, которое не всегда нуждается в защите.

Кроме того, C ++, как правило, не любит добавлять подобный код в ваш код без явного запроса. Обычно это делается с точки зрения целых функций, а не части функций. Даже вызовы деструкторов в конце блоков - это то, что вы просили, когда кладете объект, подлежащий уничтожению, в стек.

18 голосов
/ 26 мая 2019

Существуют алгоритмы, где это может произойти.

  1. Вы знаете, что lhs и rhs могут быть одинаковыми, но выполнить это проще, чем проверить.Например, рассмотрим a = std::min(a,b); - проще и, возможно, легче понять, чем if (a > b) a = b; - теперь рассмотрим более сложные примеры подобных вещей.

  2. Вы не знаете, могут ли быть lhs и rhsто же самое, потому что они могли быть переданы откуда-то еще.

Эти алгоритмы, где это может произойти, не редкость.

0 голосов
/ 27 мая 2019

Должен признать, что никогда не слышал о таких общеизвестных знаниях. Для не POD-объектов более строгим подходом является запрет на их копирование с отключением конструктора копирования и оператора присваивания. Так что у вас нет проблемы вообще.

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

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

Например, следующий код:

class A {
    int a;
    double b;
};

A& foo(A& input)
{
    return (input = input);
}

компилируется в (gcc 4.9, -O2):

_Z3fooR1A:
    .cfi_startproc
    movq    %rdi, %rax
    ret
    .cfi_endproc

Который ничего не копирует.

...