C++——关于返回值优化问题

我们知道,对于一个函数的返回值来说,其是一个对象的拷贝。并且应当是一个右值。我们现在有一个函数

A get_A()
{
    A a(1);
    return a;
}

int mian()
{
    A = get_A();
    return 0;
}

这个函数的行为应当是在函数体中构造一个a,然后在返回的时候再复制一份a。

我们的本意是仅仅的得到一份a,但是却做了重复的拷贝工作。在编译器还不是那么智能的年代,这个问题不能得到很好的解决。但是现代C++中,编译器会为我们做优化。我们如上调用这个函数,编译器我们生成的目标代码就像是直接调用了A的构造函数一样。

struct A
{
    int data;
    A() : data(0)
    { 
        cout << "construction" << endl;
    }
    A(int d) : data(d)
    {
        cout << "construction" << endl;
    }
    A(A && a) : data(a.data)
    {
        cout << "move" << endl;
    }
    A(const A & a) : data(a.data)
    {
        cout << "copy con" << endl;
    }
    ~A()
    {
        cout << "destruction" << endl;
    }
};
A get_A()
{
    A a(1);
    return a;
}
A get_A_p()
{
    return A(1);
}

int main()
{
    A a1 = get_A();
    // A a2 = get_A_p()
    cin.get();
    return 0;
}

你可以拿着这么一个简短的代码去g++, 或者clang++,或者是任何一个支持返回值优化的编译上去运行,你将会得到如下的结果。

所以说,如果有编译器的优化,在些C++代码的时候关于返回值这里可以放心的去写。效率的工作编译器为我们承担了一部分。


不过为读者困惑的是,即便关闭了编译器优化,我么得到的目标文件还是可以进行返回值优化,如果想要关闭这种优化来看一下返回值具体的运作过程,需要使用以下的命令。

 g++ main.cpp -o main -fno-elide-constructors

运行程序

 

这是一个惊人的结果!如果在对象比较大的时候还没有对应的移动构造,那么进行的无用复制可想而知。

来分析以下:

首先是函数内的A a(1) 对象,返回值用其移动构造,然后a自身销毁,返回值又移动构造给了main中的a1然后销毁。这个开销还是很大的。不过好在有编译器为我们优化,在现代C++设计中,有编译器的优化,我们可以不用在返回值的效率上杞人忧天了,岂不是十分舒服!

参考:

More Effective C++条款19,20

 g++ 关闭返回值优化_天涯Kevin的博客-优快云博客_g++ 关闭优化

### C++函数返回值的本质 在 C++ 中,当一个函数有非 `void` 的返回类型时,该函数内的每条 `return` 语句必须返回一个。此返回值的类型需与函数声明的返回类型相匹配或可隐式转换为目标类型[^1]。 对于函数返回临时对象的情况,在调用者接收返回值的过程中涉及到了内存管理机制。考虑下面的例子: ```cpp #include <iostream> #include <string> using namespace std; string func() { string a = "hello world"; cout << "&a = " << &a << endl; return a; } int main () { string A; cout << "&A = " << &A << endl; A = func(); cout << A << endl; return 0; } ``` 上述代码展示了如何通过栈上创建的对象 `a` 来初始化另一个栈上的变量 `A`。实际上,编译器通常会对这种场景应用一种优化技术——返回值优化 (RVO),即直接将要返回的对象构建到目标位置,从而避免不必要的拷贝操作[^2]。 如果函数返回的是局部对象的一个引用而非副本,则需要注意生命周期问题。因为一旦超出定义这些对象的作用域范围,它们就会被销毁,此时再访问这样的引用会导致未定义行为。因此,除非确实有必要并确保安全的情况下(比如返回类员变量的引用),一般不建议这样做[^3]。 另外,当从函数内部返回某个表达式的计算结果或是其他具有更长生存期的对象时,C++ 编译器可能会自动生一个临时对象用于存储这个结果,并将其传递给外部环境。这意味着即使原始数据结构已经离开了它的作用域,程序仍然可以正常工作,因为它依赖的是新建立起来的那个独立存在的实体而不是原版的数据源[^4]。 #### 关键点总结 - 函数内任何 `return` 语句都应提供适当类型的。 - 返回临时对象时可能触发 RVO 或 NRVO(命名返回值优化)以提高效率。 - 应谨慎处理返回局部对象引用的情形以防遇到悬垂指针风险。 - 当返回复杂对象实例时,实际上传递给外界的是一个新的临时对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值