Подтвердить что ты не робот

Удаленный конструктор по умолчанию может быть тривиальным?

Глядя на определение тривиального конструктора по умолчанию в стандартах:

Конструктор по умолчанию является тривиальным, если он не предоставляется пользователем, и если:

  • его класс не имеет виртуальных функций (10.3) и нет виртуальных базовых классов (10.1) и
  • нет нестатического элемента данных его класса, который имеет инициализатор скобок или равный, и
  • все прямые базовые классы его класса имеют тривиальные конструкторы по умолчанию, а
  • для всех нестатических членов данных своего класса, которые относятся к типу класса (или его массиву), каждый из таких классов имеет тривиальное значение по умолчанию конструктор.

В противном случае конструктор по умолчанию является нетривиальным.

Похоже, что определение тривиальности конструктора по умолчанию не исключает возможности конструктора deleted по умолчанию:

struct A {
    int& a;  // the implicitly defaulted default constructor will be defined as deleted
};

struct B {
    B()=delete;  // explicitly deleted
};

int main() {
    static_assert(is_trivial<A>::value, "");
    static_assert(is_trivial<B>::value, "");
}

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

Не сделал бы такой тип "trivial class" проблемой? Например, для таких вещей, как время жизни объекта, эквивалентность копии по байтам, ведомость инструкции goto и т.д.

РЕДАКТИРОВАТЬ: Следующий пример подтверждения goto недействителен. Спасибо за комментарий @Casey. Еще один пример байт-мудральной эквивалентности копии добавлен для замены этого.

В качестве примера возьмем выражение goto, стандарты говорят:

Можно передать в блок, но не таким образом, чтобы обходит объявления с инициализацией. Программа, которая прыгает87 из точка, где переменная с автоматической продолжительностью хранения не находится в сфера до точки, в которой она находится в сфере охвата, переменная имеет скалярный тип, тип класса с тривиальным значением по умолчанию конструктор и тривиальный деструктор, cv-квалифицированная версия одного из эти типы или массив одного из предыдущих типов и объявляется без инициализатора (8.5).

Итак, для следующего кода:

class A {
    int& a;
public:
    A(int& aa): a{aa} {}
    A()=default;  // this is necessary otherwise no default constructor will be implicitly declared then the type will not be trivial
};

int i;

int main() {
    static_assert(is_trivial<A>::value, "");
    goto L;
    A a{i};
L:
    return 0;
}

Он корректно сформирован в соответствии с правилами, потому что A имеет тривиальный конструктор по умолчанию и тривиальный деструктор (утверждение проходит ОК). Напротив, код плохо сформирован в С++ 03 (при синтаксисе С++ 11-only удален, т.е. в строке A()=default;), потому что A не является POD в С++ 03, а С++ 03 позволяет goto пересекать только определение типа POD.

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

Для любого тривиально-скопируемого типа T, если два указателя на T указывают на различные T-объекты obj1 и obj2, где ни obj1, ни obj2 не являются subobject базового класса, если базовые байты (1.7), составляющие obj1, являются скопированные в obj2,41 obj2, впоследствии должны иметь такое же значение, как и obj1.

So memcpy() для тривиально-скопируемого типа хорошо определен:

class A {
    int& a;
public:
    A(int& aa): a{aa} {}
    A()=default;  // this is necessary otherwise no default constructor will be implicitly declared then the type will not be trivial
    void* addr() {return &a;}
};

int i = 0;
int j = 0;

int main() {
    static_assert(is_trivial<A>::value, "");
    A a{i};
    A b{j};
    cout << a.addr() << " " << b.addr() << "\n";
    // a = b;  // this will be ill-formed because the implicitly defaulted copy assignment is defined as deleted
    memcpy(&a, &b, sizeof(A));  // this is well-defined because A is trivial
    cout << a.addr() << " " << b.addr() << "\n";
}

Он корректно определен в соответствии с правилами, потому что A является тривиальным типом (утверждение проходит ОК). Результат показывает, что делается ссылка на разные объекты в разное время. Наоборот, код undefined в С++ 03 (с синтаксисом С++ 11-only удален, т.е. строка A()=default;), потому что A не является POD в С++ 03, а С++ 03 допускает только эквивалентную копию копии типа POD.

4b9b3361

Ответ 1

CWG issue 667 рассмотрел этот точный вопрос с изменением, которое было включено в рабочий проект С++ около N3225. N3225 § 12.1 [class.ctor]/5:

Конструктор по умолчанию является тривиальным, если он не предоставляется ни пользователем, ни удаляется, и если:

  • его класс не имеет виртуальных функций (10.3) и нет виртуальных базовых классов (10.1) и
  • нет нестатического элемента данных его класса, который имеет инициализатор скобок или равный, и
  • все прямые базовые классы его класса имеют тривиальные конструкторы по умолчанию, а
  • для всех нестатических членов данных своего класса, которые относятся к типу класса (или его массиву), каждый такой класс имеет тривиальный конструктор по умолчанию.

В противном случае конструктор по умолчанию является нетривиальным.

Это было (очевидно) изменено до выпуска С++ 11. CWG DR 1135 был создан для обращения к комментарию национального органа Финляндии по проекту кандидата на С++ 11:

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

Решение этой проблемы удалило текст "или удалил" из 12.1, а также разделы, описывающие тривиальные деструкторы, тривиальные конструкторы копирования/перемещения и тривиальные операторы присваивания/перемещения. Я думаю, что это изменение обрезало слишком широкую полосу, и, вероятно, не было намерено сделать ваш struct A тривиальным. Действительно, по номинальной стоимости смешно, что эта программа плохо сформирована:

int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
a_x = a_y;

но эта программа не является, поскольку A тривиально скопируема (Clang соглашается, GCC не):

int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
std::memcpy(&a_x, &a_y, sizeof(a_x));

Существование CWG-проблемы 1496 "Тривиальность с удаленными и отсутствующими конструкторами по умолчанию" указывает на то, что комитет знает о проблеме (или, по крайней мере, тесно связанную проблема):

Конструктор по умолчанию, который определен как удаленный, является тривиальным, согласно пункту 12.1 [class.ctor]. Это означает, что согласно 9 [классу] пункта 6 такой класс может быть тривиальным. Если, однако, класс не имеет конструктора по умолчанию, поскольку он имеет конструктор, объявленный пользователем, класс не является тривиальным. Поскольку оба случая препятствуют построению класса по умолчанию, непонятно, почему существует разница в тривиальности между случаями.

хотя пока нет разрешения.