C++ 具有受保护析构函数的类数组的动态分配

C++ 具有受保护析构函数的类数组的动态分配,c++,c++11,visual-c++,language-lawyer,C++,C++11,Visual C++,Language Lawyer,如果我有一个定义如下的类 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]; 编译器(

如果我有一个定义如下的类

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-当定义构造函数时,为什么允许我创建单个对象而不是对象数组。

我不是语言律师(非常熟悉该标准),但怀疑答案与Baum mit Augen之前给出的答案一致(已删除,因此只有那些有足够声誉的人才能看到它)

如果后续数组元素的构造失败并引发异常,则需要删除已构造的元素,需要访问析构函数


但是,如果构造函数是
noexcept
,则可以排除这种情况,并且不需要访问析构函数。即使在这种情况下,gcc和clang仍然会抱怨,这很可能是一个编译器错误。也就是说,编译器没有考虑构造函数是
noexcept
。或者,编译器可能在标准范围内,在这种情况下,根据C++11,§5.3.4 17,这听起来像是标准中的缺陷

拒绝使用受保护析构函数的数组-
新的

如果新表达式创建了类类型的对象或对象数组,则对分配函数、释放函数(12.5)和构造函数(12.1)进行访问和模糊控制如果新表达式创建类类型的对象数组,则对析构函数(12.4)进行访问和模糊控制。

(增加了强调;C++03,§5.3.4¨16中使用了几乎完全相同的措辞;C++14移动了一些东西,但这似乎并没有改变问题的核心-请参见@Baum-mit-Augen的答案)

这是因为
new[]
只有在所有元素都已构造的情况下才会成功,并且希望避免在一个costructor调用失败时泄漏对象;因此,如果它设法构造(比如)前9个对象,但第10个对象因异常而失败,那么它必须在传播异常之前销毁前9个对象

请注意,如果构造函数被声明为
noexcept
,则在逻辑上不需要此限制,但该标准似乎在这方面没有任何例外



因此,在第一种情况下,gcc在技术上是错误的,就标准而言,它也应该被拒绝,尽管我认为“道德上”gcc做的事情是正确的(因为在实践中,
A
的默认构造函数无法抛出)。

事实证明,gcc在这里是不正确的。在N4141(C++14)中,我们有:

如果 新表达式创建类类型的对象数组,析构函数可能被调用(12.4)

(5.3.4/19[新增版本])和

A 如果可能被调用的析构函数被删除或无法从上下文访问,则程序的格式不正确 调用的一部分

(12.4/11[类别dtor])所以这两个数组案例都应该被拒绝。(Clang确实做到了这一点,)

原因是,正如其他人和我以前错误的回答所提到的,类类型元素的构造可能会因异常而失败。发生这种情况时,必须调用所有完全构造元素的析构函数,因此析构函数必须是可访问的


当使用
运算符new
分配单个元素时(不使用
[]
),该限制不适用,因为如果单个构造函数调用失败,就不可能有完全构造的类实例。

刚刚用clang(Apple LLVM版本8.0.0 clang-800.0.42.1)对此进行了测试。它的行为与gcc不同,无论
A
是否有明确声明的公共构造函数,或者所述构造函数是否为
noexcept
,它都会在
new A[10]
上投诉。感谢您挖掘我们的标准。这正是我所怀疑的:标准中的缺陷。@Walter:标准不支持人们想要做的一切,如果支持的话,技术上也可以做的一切。这不是缺点,这是可能的缺点。这是主观的,因为对其他人来说,避免标准中更复杂的内容,可能更有价值。类似的缺点:标准不支持反变量参数。同样的观点也适用:虽然有些人可能想要它,但对其他人来说,这只会增加边际收益(如果有的话)的复杂性。关于标准措辞,拥有
noexcept
构造函数可能不足以保证在构建数组期间不会抛出异常。构造函数可能有一个参数,该参数带有调用中使用的默认参数,初始化可能会抛出该参数。我不认为处理像问题中的例子这样的简单案例值得在措辞上增加复杂性。此外,此处指定的方式与[class.base.init]中处理其他类型子对象的方式一致。抄送@Walter@bogdan对于一致性来说没问题,但是了解默认构造函数是如何具有参数的吗?如果一个类有一个构造函数
the_class::the_class()noexcept
并且它抛出,那么必须调用
terminate()
。@Walter
the_class::the_class(other_class=other_class{})noexcept
是一个默认的构造函数。为什么在
new a[10]中可能调用析构函数
A::A()无异常时?@Walter Standard说