使用-fno-elide-constructors时,C++11传值函数临时对象销毁时机疑问
C++11中函数按值传参的临时对象销毁时机问题
我把代码从C17改成C11后,遇到了关于临时对象销毁时机的困惑,以下是相关代码、编译命令和输出结果:
代码示例
#include <cstdio> #include <iostream> #include <initializer_list> using namespace std; class A { public: static int s; int t = s; A(){ cout << "constructed" << t<< endl; s++; } A (A&& a) { cout << "in move ctor, moving"<< a.t << " creating" << s << endl; s++; } ~A() { cout << "deleting"<< t << endl; } }; A f1 (A a) { std::cout << "f1: " << endl; return a; } int A::s = 0; int main() { f1(A()) ; std::cout << "is still alive!" << endl; }
编译命令
g++ -std=c++11 -fno-elide-constructors -Wall -pedantic -pthread main.cpp && ./a.out
输出结果
constructed0
in move ctor, moving0 creating1
f1:
in move ctor, moving1 creating2
deleting2
deleting1
deleting0
is still alive!
我的疑问
我无法理解该结果:若f1按值接收A类型参数,传入的是A&&临时对象,那么f1内部应创建临时对象"a"(对应输出中的1),按预期该对象应在f1退出时销毁,但实际它和其他临时对象一起在该行代码结束时销毁。请问:专为函数创建的对象是在函数结束时销毁,还是在创建它的代码行结束时销毁?
解答
咱们结合C++11的标准规则一步步拆解这个问题:
先理清对象的创建链
- 调用
f1(A())时,首先会生成第一个临时对象temp0(对应输出的constructed0); - 因为
f1是按值传参,所以会用temp0移动构造出函数的形参a(也就是输出里的in move ctor, moving0 creating1,这个a的t值是1); - 你禁用了返回值优化(
-fno-elide-constructors),所以函数返回a时,又会从a移动构造出返回值临时对象ret_temp(对应in move ctor, moving1 creating2,这个对象的t值是2)。
- 调用
为什么形参
a没在函数结束时销毁?- 正常情况下,函数形参确实会在函数执行完毕后立即销毁,但C++11有个特殊规则:当函数返回的是自动存储期的对象(比如这里的形参
a),并且该对象被用来初始化函数调用的返回值临时对象时,形参的生命周期会被延长到整个函数调用表达式结束时。 - 简单说,
f1(A())这一整行是一个完整表达式,它产生的返回值临时对象ret_temp会在这行代码执行完毕后才销毁。而形参a、最初的临时对象temp0的销毁时机被绑定到了ret_temp上,所以三者会在同一时刻被销毁,这就是你看到三个deleting输出都在is still alive!之前的原因。
- 正常情况下,函数形参确实会在函数执行完毕后立即销毁,但C++11有个特殊规则:当函数返回的是自动存储期的对象(比如这里的形参
如果开启返回值优化会怎样?
- 要是去掉
-fno-elide-constructors,C++11允许编译器直接省略中间的两次移动构造:直接把最初的temp0当作形参a使用,再直接作为返回值返回,这样整个流程只会有一次构造和一次销毁,就不会出现多个对象延迟销毁的情况了。
- 要是去掉
总结一下:
- 普通场景下,函数形参会在函数执行完毕后立即销毁;
- 但当形参被用来初始化函数返回值临时对象时(尤其是禁用返回值优化的场景),它的生命周期会被延长到整个函数调用表达式结束,和其他相关临时对象一起销毁。
内容的提问来源于stack exchange,提问作者DsCpp
相关产品推荐
相关产品推荐

