Конструктор по умолчанию T обходит, когда {} используются с новым для создания std :: array - PullRequest
2 голосов
/ 10 февраля 2020

Давайте рассмотрим этот класс:

class A {
public:
    A() = delete;

    A(int i) :
            i_m(i) {
        std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
    }

    ~A() {
        std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
    }

private:
    int i_m{1234567890};
};

Конструктор по умолчанию явно удален, поэтому AFAIK A может быть построен только из целого числа. Инициализация по умолчанию для элемента данных i_m никогда не будет использоваться.

Давайте рассмотрим эту программу:

int main() {
    using T = std::array<A, 2>;

    //T a;
    // error: use of deleted function 'std::array<A, 2>::array()'
    // note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed
    // error: use of deleted function 'A::A()'

    //T b{};
    // error: use of deleted function 'A::A()'
}

Опять же, мне это кажется вполне подходящим.

Давайте теперь рассмотрим эту другую программу:

int main() {
    using T = std::array<A, 2>;
    auto foo = new T{};
    delete foo;

    auto foo_init = new T{1, 2};
    delete foo_init;

//  auto zorg = new T();
//  delete zorg;
//  error: use of deleted function 'std::array<A, 2>::array()'
//  note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed:
//  error: use of deleted function 'A::A()'

    auto zorg_init = new T({3, 4});
    delete zorg_init;
}

Этот код выполняет компиляцию (без предупреждения) и генерирует следующий вывод:

A::~A() 0
A::~A() 38870160
A::A(int) 1
A::A(int) 2
A::~A() 2
A::~A() 1
A::A(int) 3
A::A(int) 4
A::~A() 4
A::~A() 3

А теперь что-то мне кажется не хорошо. Как это возможно, что линия auto foo = new T{}; не считается неправильной? Инициализация класса A здесь полностью обойдена.

Ничего не стоит, если этот код не компилируется:

int main() {
    auto p = new A{};
    delete p;
}

Ожидаемая ошибка:

error: use of deleted function 'A::A()'

Я тестировал эти коды со следующими параметрами: -Wall -Wextra -pedantic -std=c++17. gcc -v дает: gcc version 7.2.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) на моем компьютере.

Любое объяснение будет с благодарностью:)


PS: после исчерпывающих тестов в Compiler Explorer (используется с примером ) https://godbolt.org/z/5VZLU_), похоже, это проблема в g cc 7.x. Действительно, только 7.x версии g cc компилируют этот пример. г cc 6,4 нет. г cc 8,1 нет. ни лязгать ни msv c none.

Можете ли вы подтвердить, что здесь нет UB и это действительно ошибка компилятора?

1 Ответ

0 голосов
/ 10 февраля 2020

Тот факт, что T b{}; не компилируется, а new T{} делает очевидным, что это ошибка компилятора в любом случае. Однако сам стандарт, вероятно, неисправен. И вот почему.


Технический ответ на этот вопрос таков: код правильно сформирован , потому что в стандарте это прямо сказано. [array.overview] / 2

array - это агрегат, который может быть инициализирован списком максимум с N элементами, типы которых могут быть преобразованы в T .

Инициализатор {} действительно содержит «до N элементов, типы которых могут быть преобразованы в T» - он содержит 0 элементов, все из которых являются конвертируемыми на A.


Как инициализируются элементы? Стандарт не совсем ясно по этому вопросу. Вот что говорит [array.overview] / 3 :

array удовлетворяет всем требованиям контейнера и обратимого контейнера ([container.requirements]) , за исключением того, что построенный по умолчанию объект array не является пустым и что swap не имеет постоянной сложности. [...]

(выделено мной)

В стандарте не упоминается, как инициализируются элементы этого непустого array.


Предположим, что std::array реализован так (для N == 2):

template <class T, std::size_t N>
struct array {
    T __elem[N];
};

Затем элементы инициализируются копией из {} в соответствии с [dcl.init.aggr] / 8 :

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

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

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

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

[. ..]

, который неправильно сформирован согласно [dcl.init.list] / 3 (удаленный конструктор по умолчанию выбран).

Это означает, что стандарт рассматривает общую реализацию для быть неправым, что, вероятно, не предназначено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...