You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 场景1的本质:命名返回值优化(NRVO)
    编译器会直接在main函数中obj1的内存位置上构造generateRes里的temp对象——不需要先在generateRes的栈帧中构造temp,再通过复制构造把它拷贝到obj1的位置。因此你看到的Cons called其实就是obj1的构造,而非单独的temp构造,自然不会再触发一次obj1的构造函数。

  2. 场景2的本质:返回值优化(RVO)
    直接返回匿名临时对象时,编译器同样会跳过临时对象的创建,直接在obj1的内存位置上构造这个AutoPtr<Reference>实例。你感知到的"main中构造函数被调用",本质还是同一次构造,和场景1的优化逻辑一致,只是没有命名对象的中间环节。

  3. 为什么普通构造函数不会被调用两次?
    复制消除的核心就是避免创建多余的对象:原本需要构造两个对象(函数内的临时/命名对象,以及main里的obj1),优化后变成只在最终目标位置(obj1的内存)构造一次对象,因此普通构造函数只会执行一次,不会出现两次调用的情况。

这种优化是C标准明确允许的(C17之后部分场景为强制优化),即便复制构造函数带有副作用(比如这里的打印),编译器也有权跳过复制/移动构造,直接合并对象的构造流程。


内容的提问来源于stack exchange,提问作者Rogmier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 03:45:40