这个问题是基于Scott在他的书“更有效的C++”中提供的一个例子。请考虑以下课程:
// A class to represent the profile of a user in a dating site for animal lovers.
class AnimalLoverProfile
{
public:
AnimalLoverProfile(const string& name,
const string& profilePictureFileName = "",
const string& pictureOfPetFileName = "");
~AnimalLoverProfile();
private:
string theName;
Image * profilePicture;
Image * pictureOfPet;
};
AnimalLoverProfile::AnimalLoverProfile(const string& name,
const string& profilePictureFileName,
const string& pictureOfPetFileName)
: theName(name)
{
if (profilePictureFileName != "")
{
profilePicture = new Image(profilePictureFileName);
}
if (pictureOfPetFileName != "")
{
pictureOfPet = new Image(pictureOfPetFileName); // Consider exception here!
}
}
AnimalLoverProfile::~AnimalLoverProfile()
{
delete profilePicture;
delete pictureOfPet;
}Scott在他的书中解释说,如果在对象的构造函数中抛出异常,那么该对象的析构函数将永远不会被调用,因为C++不能破坏部分构造的对象。在上面的例子中,如果调用new Image(pictureOfPetFileName)抛出一个异常,那么类的析构函数就不会被调用,这会导致已经分配的profilePicture泄漏。
他描述了处理这个问题的许多不同方法,但我感兴趣的是成员变量theName。如果构造函数中任何一个对new Image的调用都抛出了一个异常,那么这个成员变量不会泄漏吗?斯科特说它不会被泄露,因为它是一个非指针的数据成员,但是如果AnimalLoverProfile的析构函数从未被调用,那么谁破坏了theName?
发布于 2018-01-16 14:02:48
AnimalLoverProfile的析构函数从未被调用,因为该对象尚未构造,而theName的析构函数将被调用,因为该对象的构造是正确的(尽管它是一个尚未完全构造的对象的字段)。在这里使用智能指针可以避免任何内存泄漏:
::std::unique_ptr<Image> profilePicture;
::std::unique_ptr<Image> pictureOfPet;在这种情况下,当new Image(pictureOfPetFileName)抛出时,已经构造了profilePicture对象,这意味着它的析构函数将被调用,就像调用theName的析构函数一样。
发布于 2018-01-16 14:06:07
斯科特是对的。考虑一下类的不化步骤:
1)如果构造函数用于派生最多的类,则按照基类声明的深度第一次从左到右遍历的顺序初始化虚拟基类(左到右是指基类说明符列表中的外观)。 2)然后,按从左到右的顺序初始化直接基类,就像它们出现在该类的基类说明符列表中一样。 3)然后,根据类定义中的声明顺序初始化非静态数据成员。 4)最后,执行构造函数的主体
这意味着,在进入构造函数主体之前,已经初始化了数据成员。如果在构造函数体内抛出任何异常,则不会调用类的析构函数(因为构造函数尚未完成),但是数据成员将通过其析构函数(即std::string::~string() for theName)被销毁,因为它们的初始化已经完成。这就是为什么对于此类异常保证问题,我们应该使用智能指针而不是原始指针。
发布于 2018-01-16 14:06:32
如果在构造过程中抛出异常,则所有已构造的子对象都将被销毁。请参阅[except.ctor]/3
如果委托构造函数以外的对象的初始化或销毁被异常终止,则对对象的每个直接子对象调用析构函数,对于完整对象,调用虚拟基类次subobjects,其初始化已完成(dcl.init),且其析构函数尚未开始执行,但在销毁的情况下,联合类的变体成员未被销毁。这些次级物体的破坏顺序与其建造完成的顺序相反。在输入构造函数或析构函数的函数尝试块(如果有的话)的处理程序之前,会对这种销毁进行排序。
Nota,我刚刚发现,即使是第一个初始化的变体成员也被销毁了,所以在构造函数中,如果您更改了变体的活动成员,那么在它的生命周期内,灭活的成员仍然会被销毁!
https://stackoverflow.com/questions/48282948
复制相似问题