Равномерная инициализация не копируемого члена данных класса приводит к ошибке gcc - PullRequest
2 голосов
/ 06 ноября 2019

Предположим, у нас есть этот код:

class A {
public:
    A() = default;    
    A(const A&) = delete;
    ~A() = default;
};

class B {
public:    
    B() : a{} { }    
    A a[1];
};

int main() 
{
    B b;
}

Этот код компилируется на последних версиях GCC 9.2, Clang 9.2 и MSVC 19.22.

Но когда я изменяю деструктор A по умолчанию на ~A() { } GCC возвращает ошибку use of deleted function 'A::A(const A&)'. Clang и MSVC все еще компилируются.

Когда я пишу конструктор копирования A, GCC компилируется, но во время выполнения этот конструктор никогда не вызывался. Для чего GCC нужен конструктор копирования? Это ошибка GCC? (Я пробовал все версии GCC на GodBolt.org, та же ошибка.)

1 Ответ

1 голос
/ 06 ноября 2019

Это ошибка GCC.

Конструктор по умолчанию B использует агрегатную инициализацию для инициализации a без инициализаторов. [dcl.init.aggr] / 8 :

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

  • Если элемент имеет инициализатор члена по умолчанию ([class.mem]), элемент инициализируется из этого инициализатора.

  • В противном случае, если элемент не является ссылкой, элемент инициализируется копией из пустого списка инициализатора ([dcl.init.list]).

  • В противном случае программа некорректно сформирована.

[...]

То есть a[0] * инициализируется при копировании из {}, то есть copy-list-initialization . Вероятно, именно здесь GCC начинает путаться - инициализация копирования не обязательно включает конструктор копирования.

[dcl.init.list] /3.4:

Список-инициализация объекта или ссылки типа T определяется следующим образом:

  • [...]

  • В противном случае, если список инициализаторов не имеет элементов и T является типом класса с конструктором по умолчанию, объект инициализируется значением.

  • [...]

Следовательно, конструктор по умолчанию A используется напрямую. Там не участвует конструктор копирования. Не тривиальность.


Если вы беспокоитесь о разнице между C ++ 11 и C ++ 17, N3337 [dcl.init.aggr] / 7 :

Если в списке инициализаторов меньше, чем элементов в совокупности, то каждый элемент, не инициализированный явно, должен быть инициализирован из пустого списка инициализатора ([dcl.init.список]). [...]

Инициализация копирования здесь даже не используется. И N3337 [dcl.init.list] /3.1:

Список-инициализация объекта или ссылки типа T определяется следующим образом:

  • Если список инициализаторов не имеет элементов и T является типом класса с конструктором по умолчанию, объект инициализируется значением.

  • [... ]

Без изменений.

...