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

Динамическое распределение массива классов с защищенным деструктором

Если у меня есть класс, определенный как

class A {
protected:
    ~A(){ }
};

то я могу динамически выделять отдельную группу, а также массив таких объектов, как

A* ptr1 = new A;
A* ptr2 = new A[10];

Однако, когда я определяю конструктор для этого класса

class A {
public:
    A(){}
protected:
    ~A(){ }
};

то я могу создать отдельные объекты с помощью

A* ptr = new A;

но когда я пытаюсь динамически выделять массив объекта с помощью

A* ptr = new A[10];
Компилятор

(gcc-5.1 и Visual Studio 2015) начинает жаловаться, что A:: ~ A() недоступен.

Может ли кто-нибудь объяснить: -

1- Почему разница в поведении с конструктором определяется и не определена.

2- Когда конструктор определен, почему мне разрешено создавать отдельный объект, а не массив объекта.

4b9b3361

Ответ 1

Отказ от массива new с защищенным деструктором является правильным, как в С++ 11, §5.3.4 ¶17:

Если новое выражение создает объект или массив объектов типа класса, управление доступом и двусмысленностью выполняется для функции распределения, функции освобождения (12.5) и конструктора (12.1). Если новое выражение создает массив объектов типа класса, для деструктора (12.4) выполняются контроль доступа и двусмысленности.

(выделено выделение, почти точно такая же формулировка используется в С++ 03, §5.3.4 ¶16; С++ 14 перемещает некоторые вещи вокруг, но это, похоже, не меняет суть проблемы - см. ответ @Baum mit Augen)

Это происходит из-за того, что new[] преуспевает только в том случае, если только все элементы были сконструированы, и хочет избежать утечки объектов в случае сбоя одного из вызовов бизнес-схемы; таким образом, если ему удастся построить, скажем, первые 9 объектов, но 10-я неудача с исключением, она должна уничтожить первые 9 перед распространением исключения.

Обратите внимание, что это ограничение логически не требуется, если конструктор был объявлен как noexcept, но, тем не менее, стандарт не имеет никакого исключения в этом отношении.


Итак, здесь gcc технически ошибочен в первом случае, который, насколько это касается стандарта, также должен быть отклонен, хотя я бы сказал, что "морально" gcc делает правильные вещи (как на практике там так что конструктор по умолчанию A никогда не может бросить).

Ответ 2

Как оказалось, gcc здесь неверна. В N4141 (С++ 14) мы имеем:

Если new-expression создает массив объектов типа класса, деструктор потенциально вызывается (12.4).

(5.3.4/19 [expr.new]) и

а программа плохо сформирована, если деструктор, который потенциально вызывается, удаляется или недоступен из контекста вызова.

(12.4/11 [class.dtor]). Поэтому оба аргумента массива должны быть отклонены. (Clang делает это правильно, вживую.)

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

Это ограничение не применяется при распределении одного элемента с operator new (без []), потому что не может быть полностью сконструированного экземпляра класса, если вызов одного конструктора терпит неудачу.

Ответ 3

Я не юрист по языку (очень знакомый со стандартом), но подозреваю, что ответ соответствует тому, что было дано ранее Baum mit Augen (удалено, поэтому его могут видеть только те, у кого достаточная репутация).

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

Однако, если конструктор noexcept, это может быть исключено, и доступ к деструктору не требуется. Тот факт, что gcc и clang оба по-прежнему жалуются даже в этом случае, вполне может быть ошибкой компилятора . То есть компилятор не учитывает, что конструктор noexcept. В качестве альтернативы, компиляторы могут быть в пределах стандарта, и в этом случае это пахнет как дефект в стандартном.