Динамическое распределение массива классов с защищенным деструктором
Если у меня есть класс, определенный как
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- Когда конструктор определен, почему мне разрешено создавать отдельный объект, а не массив объекта.
Ответы
Ответ 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
. В качестве альтернативы, компиляторы могут быть в пределах стандарта, и в этом случае это пахнет как дефект в стандартном.