【C++】C++中函数返回的局部对象的生命周期

函数返回的局部对象的生命周期

本文探索,一个函数中返回的局部对象的生命周期是多少?

(省流版)探索结论:在C++11及以上,函数返回值的复制构造可能会被移动构造替代(即是否有返回值优化(RVO))。如果是复制构造,局部对象在被复制一份(一份在局部函数中被析构,复制的那一份会在返回后生命周期结束再被析构一次);如果是移动构造,就不会析构两次

一、之前的想法以及验证

之前一直认为是这样的,函数中创建的局部对象是会在函数结束时进行销毁的,如果要return这个局部对象,就得先去进行复制构造。但是这样的话总感觉太浪费资源了所以就有点不相信这个是对的。然后来到ubuntu18用g++(版本是7.5.0,支持C++14)编译验证。(看完全文,就知道这个想法在没有RVO时就是对的,有RVO就是错的)

#include <iostream>
#include <string>
using std::cout;
using std::string;

class Fox
{
    string m_name;
public:
    Fox(string name="nick"){
        m_name = name;
    }
    void bark()
    {
        cout << "bark\n";
    }

    ~Fox()
    {
        cout << m_name <<" aowu....\n";
    }
};

Fox getFox()
{
    Fox fox;
    Fox fox2("nick2");
    return fox;
}

int main()
{
    Fox fox = getFox();
    return 0;
}
nick2 aowu....
nick aowu....

其中,只有一个nick析构了,所以nick在getFox函数结尾就没有发生析构,而是到main结尾才析构了。这个与我之前的想法不一致。

如果修改getFox()函数如下

Fox getFox()
{
    Fox fox;
    return fox;
}
nick aowu....

更加明显的看到只有一个nick析构。

二、GPT分析原因

在你的代码中,Fox 类的析构函数会在对象生命周期结束时被调用,并打印出对象的名称和字符串 "aowu...."。程序运行时的输出为:

复制

nick2 aowu....
nick aowu....

这个输出可以通过分析对象的生命周期来解释。以下是代码执行过程中对象的创建和销毁过程:

1. getFox() 函数的执行

getFox() 函数中:

  • 创建了一个局部对象 fox,默认构造函数被调用,m_name 被初始化为 "nick"

  • 创建了一个局部对象 fox2,构造函数被调用,m_name 被初始化为 "nick2"

  • 函数返回时,fox 被返回,而 fox2 是局部对象,会在函数结束时销毁。

  • 因此,fox2 的析构函数会被调用,输出:

    nick2 aowu....
    

2. main() 函数的执行

main() 函数中:

  • Fox fox = getFox(); 这一行代码调用了 getFox() 函数。

  • getFox() 返回的 fox 对象会通过复制构造函数(或移动构造函数,取决于编译器优化)被赋值给 main() 中的 fox

  • main() 函数中的 fox 对象会在程序结束时销毁,因此它的析构函数会被调用,输出:

    nick aowu....
    

关于对象生命周期的关键点

  • 局部对象的生命周期:在函数中定义的局部对象(如 fox2)会在函数结束时销毁。
  • 返回对象的生命周期getFox() 返回的对象 fox 会被复制(或移动)到 main() 中的 fox,而原局部对象 fox 在函数结束时也会销毁。
  • 析构函数的调用顺序:析构函数会在对象生命周期结束时被调用,按照对象销毁的顺序依次执行。

输出的顺序

  1. fox2getFox() 函数结束时销毁,输出:

    nick2 aowu....
    
  2. main() 函数中的 fox 在程序结束时销毁,输出:

    nick aowu....
    

总结

输出的顺序是由对象销毁的顺序决定的。fox2 是局部对象,先销毁;foxmain() 中的对象,后销毁。因此,输出为:

复制

nick2 aowu....
nick aowu....

GPT的回答中提到了一个函数返回对象的生命周期的点,再问…

三、GPT解释–函数返回对象的生命周期

在C++中,函数返回对象的生命周期是一个非常重要的概念,涉及到对象的创建、复制(或移动)、以及销毁的过程。理解这些过程可以帮助你更好地掌握C++的内存管理和对象生命周期管理。以下是对函数返回对象生命周期的详细解释:


1. 函数返回对象的基本原理

当函数返回一个对象时,会发生以下过程:

  • 创建对象:在函数内部创建一个对象。
  • 返回对象:函数返回时,将该对象传递给调用者。
  • 销毁临时对象:函数内部的局部对象在函数结束时销毁。
  • 调用者的对象生命周期:返回的对象在调用者的上下文中继续存在,直到其生命周期结束。

2. 返回值优化(Return Value Optimization, RVO)

C++标准允许编译器进行返回值优化(RVO),以减少不必要的对象复制或移动。RVO 是一种编译器优化技术,允许编译器直接将返回的对象构造在调用者的变量中,而不是先构造一个临时对象再复制或移动。

示例代码

cpp复制

class Fox {
public:
    Fox() { cout << "Fox constructed\n"; }
    Fox(const Fox&) { cout << "Fox copied\n"; }
    ~Fox() { cout << "Fox destroyed\n"; }
};

Fox getFox() {
    Fox fox;
    return fox;
}

int main() {
    Fox fox = getFox();
    return 0;
}
可能的输出(无RVO)

如果没有RVO,输出可能是:

复制

Fox constructed    // 构造函数调用:创建函数内的局部对象
Fox copied         // 复制构造函数调用:将局部对象复制到返回值
Fox destroyed      // 析构函数调用:销毁函数内的局部对象
Fox destroyed      // 析构函数调用:销毁调用者的对象
可能的输出(有RVO)

如果编译器启用了RVO,输出可能是:

复制

Fox constructed    // 构造函数调用:直接在调用者的变量中构造对象
Fox destroyed      // 析构函数调用:销毁调用者的对象

3. 返回对象的生命周期

函数返回的对象的生命周期取决于它被存储的位置:

  • 如果返回的对象被赋值给一个变量(如 Fox fox = getFox();),则该对象的生命周期与该变量的生命周期相同。
  • 如果返回的对象被用于临时表达式(如 getFox().bark();),则该对象的生命周期会在表达式结束时结束。
示例代码
Fox getFox() {
    Fox fox;
    return fox;
}

int main() {
    Fox fox = getFox();  // 返回的对象被赋值给变量
    getFox().bark();     // 返回的对象被用于临时表达式
    return 0;
}
生命周期分析
  • Fox fox = getFox();
    • 返回的对象被赋值给变量 fox,其生命周期与 fox 的生命周期相同。
    • main() 函数结束时销毁。
  • getFox().bark();
    • 返回的对象被用于临时表达式,其生命周期在表达式结束时结束。
    • 在调用 bark() 之后立即销毁。

4. 返回值的复制/移动

在C++11及更高版本中,返回值的复制构造可能会被移动构造替代,以提高效率。移动构造函数会“窃取”返回对象的资源,而不是复制它们。

示例代码
class Fox {
public:
    Fox() { cout << "Fox constructed\n"; }
    Fox(const Fox&) { cout << "Fox copied\n"; }
    Fox(Fox&&) { cout << "Fox moved\n"; }
    ~Fox() { cout << "Fox destroyed\n"; }
};

Fox getFox() {
    Fox fox;
    return fox;
}

int main() {
    Fox fox = getFox();
    return 0;
}
可能的输出

如果编译器启用了RVO,输出可能是:

Fox constructed
Fox destroyed

如果没有RVO,但启用了移动构造:

Fox constructed    // 构造函数调用:创建函数内的局部对象
Fox moved          // 移动构造函数调用:将局部对象移动到返回值
Fox destroyed      // 析构函数调用:销毁函数内的局部对象
Fox destroyed      // 析构函数调用:销毁调用者的对象

5. 总结

  • 返回值优化(RVO):编译器可以优化返回值的构造过程,直接在调用者的变量中构造对象,避免不必要的复制或移动。
  • 生命周期:返回的对象的生命周期取决于它被存储的位置。如果被赋值给变量,则生命周期与变量相同;如果用于临时表达式,则生命周期在表达式结束时结束。
  • 复制/移动构造:在没有RVO的情况下,返回的对象可能会触发复制构造或移动构造。

理解这些概念可以帮助你更好地管理对象的生命周期,避免不必要的性能开销和潜在的错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值