C++ 堆栈分配的对象赋值和析构函数调用

Posted

技术标签:

【中文标题】C++ 堆栈分配的对象赋值和析构函数调用【英文标题】:C++ Stack-allocated object assignment and destructor call 【发布时间】:2017-05-07 17:17:06 【问题描述】:

我试图了解在为堆栈上分配的对象分配新值时出现的一些奇怪行为(对于同一数据集,析构函数被调用两次)。我将从代码 sn-p 及其输出开始:

    class Foo 
    public:
        Foo(const string& name) : m_name(name) 
            log("constructor");
        
        ~Foo() 
            log("destructor");
        
        void hello() 
            log("hello");
        
    private:
        string m_name;
        void log(const string& msg) 
            cout << "Foo." << this << " [" << m_name << "] " << msg << endl;
        
    ;

    int main() 

        
            Foo f "f1";
            f.hello();

            f = Foo "f2";
            f.hello();
        
        cout << "scope end" << endl;
    

输出:

    Foo.0x7fff58c66a58 [f1] constructor
    Foo.0x7fff58c66a58 [f1] hello
    Foo.0x7fff58c66a18 [f2] constructor
    Foo.0x7fff58c66a18 [f2] destructor
    Foo.0x7fff58c66a58 [f2] hello
    Foo.0x7fff58c66a58 [f2] destructor
    scope end

我预期会发生什么:

0x...58 在堆栈上创建/初始化 0x...18 在堆栈上创建/初始化 Foo 析构函数在 0x...58 被调用(使用 f1 数据) Foo 析构函数在 0x...18 被调用(使用 f2 数据)

实际发生的情况:

0x...58 在堆栈上创建/初始化 0x...18 在堆栈上创建/初始化 来自 0x...18 (f2) 的数据被复制到 0x...58 Foo 析构函数在 0x...18 被调用(使用 f2 数据) Foo 析构函数在 0x...58 处被调用(也带有 f2 数据)

所以最后,Foo 析构函数被调用两次以获取相同的数据 (f2)。显然我错过了一些关于它在内部如何工作的东西,所以有人可以指出我正确的方向吗?

【问题讨论】:

您不会看到"f1" 析构函数的消息,因为您复制了一个临时对象,该对象将f 持有的名称从"f1" 更改为"f2"。因此,您首先会看到临时的 "f2" 破坏,然后是原始实例(现在名为 "f2")的破坏。注意数据不一样;它是具有相同值的副本。 如果你的类有析构函数,它几乎肯定也应该有一个拷贝构造函数和赋值运算符。 还有(可能)一个移动构造函数和移动赋值运算符(“五规则”)。 【参考方案1】:

您的实例 f 被分配了 Foo "f2" 的 副本,它不是 构造。

添加以下 operator= 覆盖以说明实际发生的情况。

Foo& Foo::operator=(const Foo& other) 
    cout << "Foo::operator=(const Foo& other)" << endl;
    m_name = other.m_name;  
    return *this;

【讨论】:

【参考方案2】:

在创建第二个Foo 对象之前,您在地址0x..58 处只有一个对象。

Address: 0x..58            Data:  m_name "f1" 
Address: 0x..18            Data: unknown

f = Foo "f2"; 行首先创建一个新的 Foo 对象,其m_name 的值为"f2",并将其存储在地址0x..18。然后它将这个对象分配给变量f

此分配不会破坏f 中先前存在的对象,它只会将数据成员复制到其中。由于 Foo 对象只有一个数据成员 m_name,因此赋值只是将第二个对象的 m_name 复制到第一个对象中。

Address: 0x..58            Data:  m_name "f2" 
Address: 0x..18            Data:  m_name "f2" 

然后为这些对象中的每一个调用析构函数。输出并不意味着同一个对象被销毁两次,它只是意味着两个对象具有相同的m_name

【讨论】:

以上是关于C++ 堆栈分配的对象赋值和析构函数调用的主要内容,如果未能解决你的问题,请参考以下文章

c++中的构造函数和析构函数

[类和对象]构造和析构

C++复制、移动、交换、赋值和析构函数的继承?我需要哪个

PHP构造函数和析构函数

C++构造和析构

C++类调用构造函数 和 析构函数的顺序