为什么从构造函数抛出异常时会发生内存泄漏?
Why is there a memory leak when an exception is thrown from a constructor?
我读了 C++ How to Program 8th Edition 一书 Paul Deitel。在第 645 页有一个声明:
When an exception is thrown from the constructor for an object that's created in a new expression, the dynamically allocated memory for that object is released.
为了验证这个说法,我写了如下代码:
#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的析构函数没有被调用,这似乎与上面的说法冲突。
我的代码是否正确以验证声明?如果不是,我应该如何修改它?如果是,是不是说明这个说法是错误的?
这意味着 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
的构造函数永远不会被替换为您的新 class 的构造函数,采用指针:
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;
}
were之前调用了A::~A()
,也就是a被解构了,我们现在有一个指针,指针不需要解构。但是分配给a的内存是不删除的。 (如果你使用智能指针std::unique_ptr<A> a = std::make_unique<A>();
,内存就会被释放,因为std::unique_ptr<A>
的析构函数被调用,它会释放内存。)
你混淆了两件事:
- 正在释放内存
- 被调用的析构函数
你已经证明后者不会发生,这是有道理的:你怎么能摧毁没有正确构造的东西?请注意,成员变量 将 调用其析构函数,因为在构造函数抛出异常时所有成员变量都已完全构造。
但这与释放内存无关,肯定会。
[C++11: 15.2/2]:
An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.
我读了 C++ How to Program 8th Edition 一书 Paul Deitel。在第 645 页有一个声明:
When an exception is thrown from the constructor for an object that's created in a new expression, the dynamically allocated memory for that object is released.
为了验证这个说法,我写了如下代码:
#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的析构函数没有被调用,这似乎与上面的说法冲突。
我的代码是否正确以验证声明?如果不是,我应该如何修改它?如果是,是不是说明这个说法是错误的?
这意味着 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
的构造函数永远不会被替换为您的新 class 的构造函数,采用指针:
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;
}
were之前调用了A::~A()
,也就是a被解构了,我们现在有一个指针,指针不需要解构。但是分配给a的内存是不删除的。 (如果你使用智能指针std::unique_ptr<A> a = std::make_unique<A>();
,内存就会被释放,因为std::unique_ptr<A>
的析构函数被调用,它会释放内存。)
你混淆了两件事:
- 正在释放内存
- 被调用的析构函数
你已经证明后者不会发生,这是有道理的:你怎么能摧毁没有正确构造的东西?请注意,成员变量 将 调用其析构函数,因为在构造函数抛出异常时所有成员变量都已完全构造。
但这与释放内存无关,肯定会。
[C++11: 15.2/2]:
An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.