为什么允许我声明一个带有已删除析构函数的对象
Why am I permitted to declare an object with a deleted destructor?
请考虑以下文本:
[C++11: 12.4/11]:
析构函数被隐式调用
- 对于程序终止时具有静态存储持续时间 (3.7.1) 的构造对象 (3.6.3),
- 对于线程退出时线程存储持续时间为 (3.7.2) 的构造对象,
- 对于具有自动存储持续时间 (3.7.3) 的构造对象,当创建对象的块退出 (6.7) 时,
对于临时对象- 生存期结束时构造的临时对象 (12.2),
- 对于由新表达式 (5.3.4) 分配的构造对象,通过使用删除表达式 (5.3.5),
- 在几种情况下,由于异常的处理 (15.3)。
如果声明了类类型的对象或其数组,并且在声明点无法访问该类的析构函数,则程序格式不正确。还可以显式调用析构函数。
那为什么这个程序编译成功呢?
#include <iostream>
struct A
{
A(){ };
~A() = delete;
};
A* a = new A;
int main() {}
// g++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
海湾合作委员会只是宽容吗?
我倾向于这样说,因为它拒绝以下内容,但该标准似乎没有特定于继承层次结构中已删除析构函数的特定规则(唯一松散相关的措辞与默认构造函数的生成有关):
#include <iostream>
struct A
{
A() {};
~A() = delete;
};
struct B : A {};
B *b = new B; // error: use of deleted function
int main() {}
第一部分不是格式不正确,因为标准文本不适用 - 那里没有声明类型 A 的对象。
对于第二部分,让我们回顾一下对象构造的工作原理。该标准说(15.2/2),如果建筑的任何部分抛出,到目前为止所有完全构造的子对象都将以与构造相反的顺序销毁。
这意味着构造函数底层的代码,如果全部是手工编写的,将如下所示:
// Given:
struct C : A, B {
D d;
C() : A(), B(), d() { /* more code */ }
};
// This is the expanded constructor:
C() {
A();
try {
B();
try {
d.D();
try {
/* more code */
} catch(...) { d.~D(); throw; }
} catch(...) { ~B(); throw; }
} catch(...) { ~A(); throw; }
}
对于更简单的类,默认构造函数(new
表达式需要其定义)的扩展代码如下所示:
B::B() {
A();
try {
// nothing to do here
} catch(...) {
~A(); // error: ~A() is deleted.
throw;
}
}
对于在完成某些子对象的初始化后可能引发异常的情况,使这项工作太复杂而无法指定。因此,这实际上不会发生,因为由于 N3797 12.1/4 中的最后一个项目符号点,B 的默认构造函数首先被隐式定义为已删除:
在以下情况下,类 X 的默认构造函数定义为已删除:
- [...]
- 任何直接或虚拟基类或非静态数据成员都具有具有析构函数的类型,该析构函数已从默认的默认构造函数中删除或无法访问。
复制/移动构造函数的等效语言作为 12.8/11 中的第四个项目符号存在。
12.6.2/10 中还有一段重要段落:
在非委托构造函数中,可能会调用每个直接或虚拟基类以及类类型的每个非静态数据成员的析构函数。
B
的析构函数是由编译器在错误行生成的,并且它有一个对A
的析构函数的调用,该析构函数被删除,因此错误。 在第一个例子中,没有任何内容试图调用A
的析构函数,因此没有错误。
我的猜测是,这就是发生的事情。
隐式生成的B()
构造函数将首先构造其类型为 A
的基类子对象。然后,该语言声明,如果在执行 B()
构造函数的主体期间引发异常,则必须销毁A
子对象。因此,需要访问已删除的~A()
- 当构造函数抛出时正式需要它。当然,由于生成的B()
体是空的,这永远不会发生,但~A()
应该是可访问的要求仍然存在。
当然,这只是 1) 只是我这边对为什么首先存在错误的猜测,以及 2) 绝不是引用标准术语来说明这是否实际上是形式上的格式错误或只是 gcc 中的实现细节。也许可以给你一个线索,看看在标准中的位置......
可访问性与删除性正交:
[C++11: 11.2/1]:
如果使用public
访问说明符将一个类声明为另一个类的基类(子句 10),则基类的public
成员可作为派生类的public
成员访问,基类的protected
成员可作为派生类的protected
成员进行访问。如果使用protected
访问说明符将一个类声明为另一个类的基类,则可以访问基类的public
和protected
成员作为派生类protected
成员。如果使用private
访问说明符将某个类声明为另一个类的基类,则可以将基类的public
和protected
成员作为派生类的private
成员进行访问。
有这个:
[C++11: 8.4.3/2]:
隐式或显式引用已删除函数(而不是声明它)的程序格式不正确。[ 注意:这包括隐式或显式调用函数,以及形成指向函数成员的指针或指针。它甚至适用于未潜在计算的表达式中的引用。如果函数重载,则仅当通过重载解析选择该函数时,才会引用该函数。—尾注 ]
但是您从不"引用"已删除的析构函数。
(我仍然无法解释为什么继承示例无法编译。
- MSVC 编译器/链接器何时合成标量/矢量删除析构函数
- 如果引用应该保留,不删除析构函数中的指针会导致内存泄漏吗?
- 删除析构函数C++中的指针
- bad_alloc::'标量删除析构函数'(无符号整数)当我尝试创建矢量 470MB 大小时
- 这是删除析构函数中的数组的正确方法吗?
- 尽管使用了 boost::scoped_ptr,我们是否应该删除析构函数中成员的指针
- 调用remove()删除析构函数中的文件是否安全
- 当存储在std::vector属性中时,我可以删除析构函数中的指针吗
- 删除析构函数中的(这个)指针
- C++ 删除析构函数中的向量类成员内存
- 删除析构函数中的指针和映射
- 尝试删除析构函数中已删除的指针
- 为什么允许我声明一个带有已删除析构函数的对象
- 为什么在删除析构函数时调用析构函数,如果未删除则不调用析构函数?
- 删除析构函数的意义何在
- std::istream子类:删除析构函数中的streambuf
- 如何安全地删除析构函数中的线程指针
- 标量删除析构函数问题
- C++11:我可以创建一个类型具有已删除析构函数的字段吗
- 为什么在 VC 中,'delete' 和 'delete []' 都使用标量删除析构函数?