关于显示调用析构函数

jeff_
  • 39

自己编写了一段代码,想着手动调用析构函数方便观察输出,但是发现手动调用析构输出对象并没有被析构,相当于析构函数只是一个普通函数(仅限当前代码,不知道是否有普适性)。于是尝试百度寻找答案(这段时间科学上网挂了),然后查资料发现之前某论坛的讨论

https://www.cnblogs.com/zsq19...
https://bbs.csdn.net/topics/3...
https://bbs.csdn.net/topics/3...
https://bbs.csdn.net/topics/3...

讨论的说法:
说法一:类对象都被销毁,不再存在了。在此之后对该对象的操作都属于未定义行为。
活法二:手动析构 == 调用函数,自动析构 == 调用函数同时销毁本身,后一个行为由系统完成,用户不能参与

因为手上没有标准书籍,这段时间也上不了外网,所以在社区请教下大家。

下面是我自己编写的代码,目的在于测试公有变量和私有变量的生存时间。

#pragma once

#include <iostream>
class test
{
private:
    int a = 0;
public:
    test();
    ~test();
    int add();
    int b = 0;
};

#include "test.h"
test::test()
{
    std::cout << "构造函数:"<< a << " " << b << std::endl;
}


test::~test()
{
    a++; b++;
    std::cout << "析构函数:" << a << " " << b << std::endl;
}

int test::add()
{
    a++; 
    std::cout << "add:"<< a << " " << b << std::endl;
    return a;
}

#include "test.h"
int main()
{
    test t0;
    std::cout << "t0.a:"<< t0.add() << std::endl;
    std::cout << "t0.b:" << t0.b << std::endl;    
    t0.b++;
    std::cout << "t0.b:" << t0.b << std::endl;
    
    //手动调用析构函数仅仅是调用了一个函数,对象本身不会被析构
    //并且当对象被销毁是系统自动再次调用析构
    //手动析构不释放内存时,自动析构不会受影响,如果手动析构释放了内存,
    //那么自动析构可能再次释放该内存,导致一种不可决定的行为
    t0.~test(); 
    t0.add();

    std::cout  << std::endl;
    test t1;

    return 0;
}
评论
阅读 891
1 个回答
✓ 已被采纳

结论:说法一,“类对象都被销毁,不再存在了。在此之后对该对象的操作都属于未定义行为。”,是正确的。

析构函数的调用开始的时候(无论显式,隐式),test类型的对象的生命周期就结束了。在test的析构函数的调用完成前,对对象的访问受限,在析构函数完成后,访问会导致UB。其中受限的部分主要是dynamic_cast,typeid和virtual function call。

6.6.3.1 [...] The lifetime of an object o of type T ends when:
(1.3) — if T is a non-class type, the object is destroyed, or
(1.4) — if T is a class type, the destructor call starts, or
(1.5) — the storage which the object occupies is released, or is reused by an object that is not nested within o (6.6.2)

6.6.3.7 Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 11.9.4. Otherwise, such a glvalue refers to allocated storage (6.6.5.4.1), and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:
(7.1) — the glvalue is used to access the object, or
(7.2) — the glvalue is used to call a non-static member function of the object, or
(7.3) — the glvalue is bound to a reference to a virtual base class (9.3.3), or
(7.4) — the glvalue is used as the operand of a dynamic_cast (7.6.1.6) or as the operand of typeid.

11.9.4.1 For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.

11.3.6.12 A destructor is invoked implicitly [...] A destructor can also be invoked explicitly.

11.3.6.14 In an explicit destructor call, the destructor is specified by a ~ followed by a type-name or decltype-specifier that denotes the destructor’s class type.

11.3.6.16 Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended (6.6.3). [Example: If the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. —end example]

关于destroy和destruct的区别:

7.5.4.3.3 [Example:
struct C { };
void f() {
    C * pc = new C;
    using C2 = C;
    pc->C::~C2(); // OK, destroys *pc
    C().C::~C(); // undefined behavior: temporary of type C destroyed twice
    using T = int;
    0 .T::~T(); // OK, no effect
    0.T::~T(); // error: 0.T is a user-defined-floating-literal (5.13.8)
}
9.3.21 Destroying an object of class type invokes the destructor of the class. Destroying a scalar type has no effect other than ending the lifetime of the object (6.6.3). Destroying an array destroys each element in reverse subscript order.

我没有找到任何段落说对class type,destroy不止是调用析构函数。虽然9.3.21没有强调调用析构函数的唯一性,但我们也没有理由认为destroy还会执行别的操作。11.3.6.16指出当析构函数执行后,这个对象便不存在了。再考虑7.5.4.3.3里的注释,标准只是由于non-class type和class type的区别所以才区分destroy和invoke of destructor的说法更可信。

撰写回答

登录后参与交流、获取后续更新提醒

宣传栏