首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在构造函数完成之前调用析构函数是否合法?

在构造函数完成之前调用析构函数是否合法?
EN

Stack Overflow用户
提问于 2013-01-22 01:40:00
回答 4查看 914关注 0票数 6

假设我有一个类,它的构造函数派生了一个删除对象的线程:

代码语言:javascript
复制
class foo {
public:
    foo() 
    : // initialize other data-members
    , t(std::bind(&foo::self_destruct, this)) 
    {}

private:
    // other data-members
    std::thread t;
    // no more data-members declared after this

    void self_destruct() { 
        // do some work, possibly involving other data-members
        delete this; 
    }
};

这里的问题是析构函数可能会在构造函数完成之前被调用。在这种情况下这是合法的吗?由于t是最后声明的(因此也是最后初始化的),并且构造函数体中没有代码,并且我从来没有打算对这个类进行子类化,所以我假设在调用self_destruct时对象已经完全初始化了。这个假设是正确的吗?

我知道在成员函数中,如果在该语句后不使用delete this;,则该语句this是合法的。但构造函数在几个方面都很特殊,所以我不确定这是否有效。

此外,如果它是非法的,我不确定如何解决它,其他在一个特殊的初始化函数中产生线程,该函数必须在对象构造之后调用,这是我真的想要避免的。

附言:我正在寻找C++03的答案(我被限制在这个项目的旧编译器上)。示例中的std::thread仅用于说明目的。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-01-22 01:58:41

首先,我们看到foo类型的对象具有非平凡的初始化,因为它的构造函数是非平凡的(§3.8/1):

如果一个对象是一个类或聚合类型,并且它或它的一个成员是由非平凡的默认构造函数初始化的,则称该对象具有非平凡的初始化。

现在我们可以看到foo类型的对象在构造函数结束后开始生命周期(§3.8/1):

T类型的对象的生命周期从以下情况开始:

获得具有类型T的正确对齐和大小的

  • 存储,如果对象具有非平凡的初始化,则其初始化已完成。

现在,如果你在构造函数末尾之前对对象进行delete,如果类型foo有一个非平凡的析构函数,这是未定义的行为(§3.8/5):

在对象的生命周期开始之前,但在对象将占用的存储空间已分配之后...可以使用指向对象将位于或曾经位于的存储位置的任何指针,但仅限于有限的方式。有关正在建设或正在销毁的对象,请参见12.7。否则,..。

因此,由于我们的对象正在构造中,我们来看一下§12.7:

成员函数,包括虚函数(10.3),可以在构造或销毁期间调用(12.6.2)。

这意味着在构造对象时调用self_destruct是很好的。但是,这一节并没有特别介绍在构造对象时销毁它。所以我建议我们看看delete-expression的运作。

首先,它“将为被删除的对象调用析构函数(如果有的话)”。析构函数是成员函数的特例,所以可以调用它。但是,§12.4析构函数没有说明在构造期间调用析构函数时是否定义良好。没什么好运气的。

其次,“删除表达式将调用释放函数”和“释放函数将释放指针引用的存储空间”。再说一次,没有提到对当前正在使用的存储执行此操作,该存储是正在构造的对象。

因此,我认为这是未定义的行为,因为标准没有非常准确地定义它。

只需注意:类型为foo的对象的生命周期在析构函数调用开始时结束,因为它有一个非平凡的析构函数。因此,如果delete this;在对象的构造结束之前发生,则在启动之前结束其生存期。这是在玩火。

票数 7
EN

Stack Overflow用户

发布于 2013-01-22 02:09:45

我敢说,它被明确定义为非法的(尽管它显然仍然可以与一些编译器一起工作)。

这在某种程度上与“构造函数抛出异常时不调用析构函数”的情况相同。

根据该标准,delete表达式将销毁派生最多的对象(1.8)或由新表达式(5.3.2)创建的数组。在构造函数结束之前,对象不是最派生的对象,而是其直接祖先类型的对象。

您的类foo没有基类,因此没有祖先,因此this没有类型,并且在调用delete时,您的对象根本不是一个对象。但是,即使有基类,对象也不是派生最多的对象(仍然将其呈现为非法的),并且会调用错误的构造函数。

票数 2
EN

Stack Overflow用户

发布于 2013-01-22 01:53:40

实际上,delete this;在大多数平台上都能正常工作;有些平台甚至可以保证作为特定于平台的扩展具有正确的行为。但是IIRC根据标准并没有很好地定义它。

您所依赖的行为是,通常可以在死对象上调用非虚拟非静态成员函数,只要该成员函数不实际访问this。但是该标准不允许这种行为;它充其量是不可移植的。

标准的3.8p6节规定,如果对象在调用非静态成员函数期间不活动,则其行为是未定义的:

类似地,在对象的生存期已经开始之前但在对象将占用的存储已经被分配之后,或者在对象的生存期已经结束之后并且在被占用的存储被重用或释放之前,可以使用引用原始对象的任何

,但仅以有限的方式。有关正在建设或正在销毁的对象,请参见12.7。否则,这样的glvalue指的是已分配的存储空间,并且使用glvalue的属性不依赖于它的值是定义良好的。在以下情况下,程序具有未定义的行为:

将左值到右值的转换应用于这样的glvalue,

  • the glvalue被用于访问非静态数据成员或调用对象的非静态成员函数,or

  • the glvalue被隐式地转换为对基类类型的引用,或者

  • glvalue被用作static_cast的操作数,除非转换最终是到cvchar&cvunsigned char&,或作为typeid.

的操作数使用

  • 作为dynamic_cast的操作数

对于这个特定的情况(删除构造中的对象),我们可以在5.3.5p2小节中找到:

...在第一种选择(删除对象)中,delete的操作数的值可以是空指针值、指向由先前的new表达式创建的非数组对象的指针、或者指向表示此类对象的基类的子对象的指针(第10条)。如果不是,则行为未定义。在第二个备选方案(删除数组)中,delete的操作数的值可以是空指针值,也可以是由前一个数组新表达式产生的指针值。如果不是,则行为未定义。

此要求未得到满足。*this不是由新表达式创建的对象,它是过去式。它是一个正在创建的对象(现在是渐进式的)。这种解释得到了数组情况的支持,其中指针必须是前一个new表达式的结果...但是new-expression还没有完全求值;它不是以前的表达式,并且还没有结果。

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

https://stackoverflow.com/questions/14444251

复制
相关文章

相似问题

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