C++返回调用者时未触发构造函数的原因探究
关于复制消除(Copy Elision)的代码疑问
测试代码
template<typename T> class AutoPtr { T* m_ref; public: AutoPtr(T* ref) { printf("Cons called\n"); m_ref = ref; } AutoPtr(const AutoPtr& autoPtr) { printf("Copy called\n"); m_ref = autoPtr.m_ref; } ~AutoPtr() { delete m_ref; } }; class Reference { public: Reference() { printf("Reference created\n"); } ~Reference() { printf("Reference deleted\n"); } }; AutoPtr<Reference> generateRes() { Reference *ref = new Reference(); //Scenario 1 //AutoPtr<Reference> temp{ref}; //return temp; //Scenario 2 return AutoPtr<Reference>{ref}; } int main() { AutoPtr<Reference> obj1 = generateRes(); return 0; }
测试场景与现象
- 场景1:先初始化命名临时对象
temp再返回,仅在初始化temp时打印Cons called,main中初始化obj1时无构造函数调用。 - 场景2:直接返回匿名临时对象,
main中初始化obj1时打印Cons called。
问题解答
这两种场景都是**复制消除(Copy Elision)**中的返回值优化(RVO/NRVO),核心是编译器直接合并了对象的构造位置,避免了多余的对象构造与复制:
场景1的本质:命名返回值优化(NRVO)
编译器会直接在main函数中obj1的内存位置上构造generateRes里的temp对象——不需要先在generateRes的栈帧中构造temp,再通过复制构造把它拷贝到obj1的位置。因此你看到的Cons called其实就是obj1的构造,而非单独的temp构造,自然不会再触发一次obj1的构造函数。场景2的本质:返回值优化(RVO)
直接返回匿名临时对象时,编译器同样会跳过临时对象的创建,直接在obj1的内存位置上构造这个AutoPtr<Reference>实例。你感知到的"main中构造函数被调用",本质还是同一次构造,和场景1的优化逻辑一致,只是没有命名对象的中间环节。为什么普通构造函数不会被调用两次?
复制消除的核心就是避免创建多余的对象:原本需要构造两个对象(函数内的临时/命名对象,以及main里的obj1),优化后变成只在最终目标位置(obj1的内存)构造一次对象,因此普通构造函数只会执行一次,不会出现两次调用的情况。
这种优化是C标准明确允许的(C17之后部分场景为强制优化),即便复制构造函数带有副作用(比如这里的打印),编译器也有权跳过复制/移动构造,直接合并对象的构造流程。
内容的提问来源于stack exchange,提问作者Rogmier
相关产品推荐
相关产品推荐

