Какая мотивация, связанная с копированием и прямой инициализацией, ведет себя по-разному?

Отчасти связано с Почему вместо конструктора преобразования вызывается конструктор конструктора?

Существует два синтаксиса для инициализации, direct- и copy-initialization:

A a(b);
A a = b;

Я хочу знать мотивацию для них, имеющих различное определенное поведение. Для инициализации копии задействована дополнительная копия, и я не могу придумать какой-либо цели для этой копии. Так как это копия из temp, она может и, вероятно, будет оптимизирована, поэтому пользователь не может полагаться на это: эрго - дополнительная копия сама по себе не является достаточной причиной для различного поведения. Итак... почему?

Ответ 1

Только спекуляция, но я боюсь, что будет трудно быть более уверенным, если Бьярн Страуструп не подтвердит, как это было на самом деле:

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

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

Ответ 2

Так как это копия из temp, она может и возможно будет оптимизирована

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

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

Идея состоит в том, что все компиляторы должны либо принимать код, либо отклонять его. В противном случае он будет не переносимым.


Другой пример, рассмотрим

A a;
B b;

A a1 = a;
A a2 = b;

Было бы несовместимо допускать a2, но запрещать a1, когда конструктор копирования A является закрытым.


Мы также видим из стандартного текста, что два метода инициализации объекта класса были предназначены для разных (8.5/16):

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

В противном случае (т.е. для остальных случаев инициализации копии) пользовательские последовательности преобразования, которые могут преобразовываться из типа источника в тип назначения или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в 13.3.1.4, а лучший выбирается с помощью разрешения перегрузки (13.3). Если преобразование не может быть выполнено или неоднозначно, инициализация плохо сформирована. Выбранная функция вызывается с выражением инициализатора в качестве аргумента; если функция является конструктором, вызов инициализирует временную версию cv-unqualified типа назначения. Временной является prvalue. Результат вызова (который является временным для случая конструктора) затем используется для прямого инициализации, согласно вышеприведенным правилам, объекта, который является местом назначения инициализации копирования. В некоторых случаях реализации разрешено исключать копирование, присущее этой прямой инициализации, путем создания промежуточного результата непосредственно в инициализированном объекте; см. 12.2, 12.8.

Разница заключается в том, что прямая инициализация напрямую использует конструкторы построенного класса. С инициализацией копирования рассматриваются другие функции преобразования, и они могут создавать временные файлы, которые необходимо скопировать.

Ответ 3

Возьмем следующий пример:

struct X
{
    X(int);
    X(const X&);
};

int foo(X x){/*Do stuff*/ return 1; }
X x(1);
foo(x);

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

Теперь подумайте с точки зрения дизайна языка, представьте себе все сценарии, о которых вам нужно подумать, если вы хотите создать правила для того, когда нужна копия, а когда нет. Это было бы очень сложно. Кроме того, даже если бы вы могли придумать правила, они были бы очень сложными и почти невозможными для понимания людьми. Однако в то же время, если вы принудительно копируете повсюду, это будет очень неэффективно. Вот почему правила такие, как они есть, вы делаете правила понятными для людей, чтобы понять, все еще не заставляя делать копии, если их можно избежать.

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

Ответ 4

Инициализация встроенных типов, таких как:

int i = 2;

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

int i(2);

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

Для встроенных типов эти два типа инициализации идентичны. В первом случае нет дополнительной копии. В этом причина наличия обоих типов инициализации и изначально не было никакого конкретного намерения заставить их вести себя по-другому.

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

Таким образом, построение копирования (например, с использованием некоторой функции преобразования) является естественной реализацией первого синтаксиса.

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