我读了 Paul Deitel 这本书 C ++如何编程第8版。第p455页有一个声明:
当从新构造函数中创建的对象的构造函数抛出异常时,将释放该对象的动态分配内存。
为了验证这个陈述,我写了如下代码:
#include <iostream>
#include <exception>
#include <memory>
class A{
public:
A(){std::cout << "A is coming." << std::endl;}
~A(){std::cout << "A is leaving." << std::endl;}
};
class B
{
public:
B()
{
std::cout << "B is coming." << std::endl;
A b;
throw 3;
}
~B(){std::cout << "B is leaving." << std::endl;}
};
int main(void)
{
try
{
std::shared_ptr<B> pi(new B);
}
catch(...)
{
std::cout << "Exception handled!" << std::endl;
}
}
输出结果为:
B is coming.
A is coming.
A is leaving.
Exception handled!
这表明B的析构函数没有被调用,这似乎与上面的陈述相冲突。
我的代码是否正确以验证声明?如果没有,我应该如何修改它?如果是,那是否意味着该陈述是错误的?
答案 0 :(得分:4)
你混淆了两件事:
你已经证明后者并没有发生,这是有道理的:你怎么能摧毁那些没有正确建造的东西?注意,成员变量将调用它们的析构函数,因为在构造函数抛出异常时,所有成员变量都已完全构造。
但这与发布的内存无关, 肯定会发生。
[C++11: 15.2/2]:
任何存储持续时间的对象,其初始化或销毁由异常终止,将为其所有完全构造的子对象(不包括类似联合的类的变体成员)执行析构函数,即对于主要构造函数(12.6.2)已完成执行且析构函数尚未开始执行的子对象。类似地,如果对象的非委托构造函数已完成执行,并且该对象的委托构造函数以异常退出,则将调用该对象的析构函数。如果对象是在new-expression中分配的,则调用匹配的释放函数(3.7.4.2,5.3.4,12.5)(如果有的话)以释放对象占用的存储空间。
答案 1 :(得分:2)
这意味着B
的ctor中的所有内容都是异常点。 B
本身的实例从未构建过,因此不得破坏它。另请注意,pi
从未构建过。
std::shared_ptr<B> pi(new B) - start with new B
new B - triggers the ctor of B
std::cout ... - the output
A b; - construct an A
throw 3; - calls ~A()
- rewind, new B is "aborted"
- std::shared_ptr<B> pi(new B) is "aborted"
你可以修改你的代码,看看std::shared_ptr
的构造函数永远不会被你的新类替换掉,只需要一个指针:
struct T {
T(B*) { std::cout << "T::T()\n"; }
};
...
try
{
T pi(new B); // instead of std::shared_ptr<B> pi(new B);
}
...
T
的构造函数不会被命中(参见“pi
从未构建过”)。
现在假设B
的构造函数将按以下方式分配内存:
B()
{
A* a = new A(); // in contrast to A a;
throw 3;
}
以前是A::~A()
被调用,这是一个解构,我们现在有一个指针,指针不需要解构。但是,分配和分配给a的内存不已删除。 (如果您使用了智能指针std::unique_ptr<A> a = std::make_unique<A>();
,则内存将被释放,因为std::unique_ptr<A>
的析构函数被调用并且它将释放内存。)