Ответ 1
Может быть полезно пройти через вызовы конструктора в обратном порядке.
B b({ A() });
Чтобы построить B
, компилятор должен вызвать конструктор B, который принимает const vector<A>&
. Этот конструктор, в свою очередь, должен сделать копию вектора, включая все его элементы. Вы увидите, что второй экземпляр ctor вы видите.
Чтобы создать временный вектор, который нужно передать конструктору B
, компилятор должен вызвать конструктор initializer_list
std::vector
. Этот конструктор, в свою очередь, должен сделать копию того, что содержится в initializer_list
*. Вы увидите, что первый конструктор копирования вы видите.
В стандарте указано, как объекты initializer_list
создаются в § 8.5.5 [dcl.init.list]/p5:
Объект типа
std::initializer_list<E>
строится из список инициализаторов, как если бы реализация выделила массив из N элементы типаconst E
** где N - количество элементов в список инициализаторов. Каждый элемент этого массива инициализируется с помощью соответствующий элемент списка инициализатора иstd::initializer_list<E>
объект построен для обращения к этому массиву.
Копирование-инициализация объекта из одного и того же типа использует разрешение перегрузки для выбора используемого конструктора (§8.5 [dcl.init]/p17), поэтому с rvalue того же типа он будет ссылаться на конструктор перемещения если таковая имеется. Таким образом, чтобы построить initializer_list<A>
из скопированного списка инициализаторов, компилятор сначала построит массив из одного const A
, перейдя из временного A
, построенного с помощью A()
, вызывая вызов конструктора перемещения, а затем постройте initializer_list
объект для обращения к этому массиву.
Я не могу понять, откуда происходит другой шаг в g++. initializer_list
обычно представляют собой не что иное, как пару указателей, а стандартные мандаты, что копирование одного не копирует базовые элементы. g++ кажется дважды вызывать конструктор перемещения при создании initializer_list
из временного. Он даже вызывает конструктор перемещения при построении initializer_list
из lvalue.
Мое лучшее предположение заключается в том, что он буквально внедряет стандартный ненормативный пример. Стандарт предоставляет следующий пример:
struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };
Инициализация будет реализована примерно так же, как и это: **
const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
предполагая, что реализация может построить объект initializer_list с парой указателей.
Итак, если вы примете этот пример буквально, массив, лежащий в основе initializer_list
в нашем случае, будет сконструирован так, как если бы:
const A __a[1] = { A{A()} };
который вызывает два вызова конструктора, потому что он создает временную A
, копирует-инициализирует второй временный A
из первого, затем копирует-инициализирует член массива из второго временного. Однако в нормативном тексте стандарта ясно, что должна быть только одна инициализация, а не две, поэтому это похоже на ошибку.
Наконец, первый A::A
поступает непосредственно из A()
.
Не так много обсуждать вызовы деструктора. Все временные (независимо от числа), созданные во время построения B
, будут уничтожены в конце инструкции в обратном порядке построения, а один A
, хранящийся в B
, будет разрушен, когда B
погаснет объема.
* Конструкторы стандартных контейнеров библиотеки initializer_list
определяются как эквивалентные вызову конструктора с двумя итераторами с list.begin()
и list.end()
. Эти функции-члены возвращают const T*
, поэтому его нельзя перенести. В С++ 14 массив подстановки выполнен const
, поэтому он еще более ясен, что вы не можете его переместить или иначе изменить.
** Этот ответ изначально цитировал N3337 (стандарт С++ 11 плюс некоторые незначительные редакционные изменения), который имеет массив, имеющий элементы типа E
, а не const E
и массив в примере имеет тип double
. В С++ 14 базовый массив был сделан const
в результате CWG 1418.