首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在C++的构造函数中抛出异常时,销毁对象的成员变量

在C++的构造函数中抛出异常时,销毁对象的成员变量
EN

Stack Overflow用户
提问于 2018-01-16 13:58:42
回答 4查看 955关注 0票数 1

这个问题是基于Scott在他的书“更有效的C++”中提供的一个例子。请考虑以下课程:

代码语言:javascript
运行
复制
// 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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2018-01-16 14:02:48

AnimalLoverProfile的析构函数从未被调用,因为该对象尚未构造,而theName的析构函数将被调用,因为该对象的构造是正确的(尽管它是一个尚未完全构造的对象的字段)。在这里使用智能指针可以避免任何内存泄漏:

代码语言:javascript
运行
复制
::std::unique_ptr<Image> profilePicture;
::std::unique_ptr<Image> pictureOfPet;

在这种情况下,当new Image(pictureOfPetFileName)抛出时,已经构造了profilePicture对象,这意味着它的析构函数将被调用,就像调用theName的析构函数一样。

票数 8
EN

Stack Overflow用户

发布于 2018-01-16 14:06:07

斯科特是对的。考虑一下类的不化步骤

1)如果构造函数用于派生最多的类,则按照基类声明的深度第一次从左到右遍历的顺序初始化虚拟基类(左到右是指基类说明符列表中的外观)。 2)然后,按从左到右的顺序初始化直接基类,就像它们出现在该类的基类说明符列表中一样。 3)然后,根据类定义中的声明顺序初始化非静态数据成员。 4)最后,执行构造函数的主体

这意味着,在进入构造函数主体之前,已经初始化了数据成员。如果在构造函数体内抛出任何异常,则不会调用类的析构函数(因为构造函数尚未完成),但是数据成员将通过其析构函数(即std::string::~string() for theName)被销毁,因为它们的初始化已经完成。这就是为什么对于此类异常保证问题,我们应该使用智能指针而不是原始指针。

票数 2
EN

Stack Overflow用户

发布于 2018-01-16 14:06:32

如果在构造过程中抛出异常,则所有已构造的子对象都将被销毁。请参阅[except.ctor]/3

如果委托构造函数以外的对象的初始化或销毁被异常终止,则对对象的每个直接子对象调用析构函数,对于完整对象,调用虚拟基类次subobjects,其初始化已完成(dcl.init),且其析构函数尚未开始执行,但在销毁的情况下,联合类的变体成员未被销毁。这些次级物体的破坏顺序与其建造完成的顺序相反。在输入构造函数或析构函数的函数尝试块(如果有的话)的处理程序之前,会对这种销毁进行排序。

Nota,我刚刚发现,即使是第一个初始化的变体成员也被销毁了,所以在构造函数中,如果您更改了变体的活动成员,那么在它的生命周期内,灭活的成员仍然会被销毁!

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48282948

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档