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

5

我正在尝试理解在将新值分配给在栈上分配的对象时出现的一些奇怪行为(析构函数会针对相同的数据集被调用两次)。我将从代码片段及其输出开始:

    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数据)

因此,对于相同的数据(f2),Foo析构函数会被调用两次。显然,我对内部的工作方式缺乏了解,所以有人能指导一下吗?


3
由于您复制了一个临时对象并将f所持有的名称从“f1”更改为“f2”,因此您将不会看到“f1”析构函数的消息。 因此,您会先看到临时的“f2”被析构,然后才是原始实例(现在命名为“f2”)被析构。请注意,数据不同,它是具有相同值的副本。 - Peter Huene
2
如果你的类有析构函数,那么它几乎肯定也应该有一个拷贝构造函数和赋值运算符。 - user2100815
还有(可能)移动构造函数和移动赋值运算符(“五法则”)。 - Peter Huene
2个回答

5

您的实例f正在被分配一个Foo {"f2"}的副本,而不是一个新的构建。

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

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

4
在创建第二个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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接