До C ++ 11 мы можем выполнить инициализацию копирования, написав что-то вроде A a = 1;
что более или менее эквивалентно A a = A(1);
, То есть сначала создается временный объект, а затем вызывается копия ctor. Независимо от исключения копирования, это должно быть так концептуально, и копия ctor должна быть доступна.
При инициализации списка в C ++ 11 мы можем выполнить инициализацию списка копирования, написав A a = {1, 2};
, На мой взгляд, это должно быть более или менее эквивалентно A a = A(1, 2);
, Тем не менее, на GCC и лязг, A a = {1, 2}
компилируется, даже когда ctor для копирования и перемещения недоступен (объявляется как private). Еще, A a = 1;
не компилируется в GCC или clang, если соответствующий ctor копирования / перемещения недоступен. Так, A a = {1, 2};
кажется более или менее эквивалентным A a{1, 2};
которая является прямой инициализацией списка. Разница между этим и реальной прямой инициализацией списка заключается в том, что A a = {1, 2};
не компилируется, если ctor, который принимает два целых числа, является явным. В этом аспекте A a = {1, 2};
напоминает инициализацию копирования.
Итак, мой вопрос: какова точная семантика выражений типа A a = {1, 2};
концептуально? От концептуально, Эмиссия копий не мешает.
Стандарт описывает это довольно хорошо; [Dcl.init.list] / 3:
[over.match.list] (выделено мной):Инициализация списка объекта или ссылки типа
T
определяется следующим образом:
- […]
- В противном случае, если
T
является типом класса, конструкторы рассматриваются.
перечислены соответствующие конструкторы и выбран лучший
через разрешение перегрузки (13.3, 13.3.1.7). Если сужение
преобразование (см. ниже) требуется для преобразования любого из аргументов,
программа плохо сформирована.
Когда объекты неагрегированного класса
T
инициализируются списком
(8.5.4), разрешение перегрузки выбирает конструктор в два этапа:
Первоначально функции-кандидаты являются конструкторами списка инициализаторов (8.5.4) класса
T
и список аргументов состоит из
список инициализаторов как один аргумент.Если жизнеспособный конструктор списка инициализаторов не найден, Разрешение перегрузки выполняется снова, где все функции-кандидаты
конструкторы классаT
и список аргументов состоит из
элементы списка инициализатора.Если список инициализатора не имеет
элементы и T имеет конструктор по умолчанию, первая фаза опущена.
В инициализации копирования списка, еслиexplicit
конструктор выбран,
инициализация плохо сформирована.
Следовательно, если конструктор списка инициализаторов не найден (как в вашем случае), элементы списка инициализаторов составляют аргументы для вызова конструктора.
На самом деле, только Разница между прямой инициализацией списка и инициализацией копирования списка заключена в последнем, выделенном жирным шрифтом предложении.
Это одно из преимуществ инициализации списка: это не требует наличия специальной функции-члена, которая в любом случае не будет использоваться.