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

函数通过不同路径返回局部变量与临时对象时的Copy elision问题

C++ Copy Elision 实际触发条件解析

定义的Foo类

class Foo {
public:
    Foo(std::string s) : s_(std::move(s)) {
        std::cout << "Constructor " << s_ << "\n";
    }
    ~Foo() {
        std::cout << "Destructor " << s_ << "\n";
    }
    Foo(const Foo& other) {
        s_ = other.s_;
        std::cout << "Copy Constructor\n";
    }
    Foo& operator=(const Foo& other) {
        std::cout << "Copy assignment\n";
        s_ = other.s_;
        return *this;
    }
    Foo(Foo&& other) {
        s_ = std::move(other.s_);
        std::cout << "Move Constructor\n";
    }
    Foo& operator=(Foo&& other) {
        std::cout << "Move assignment\n";
        s_ = std::move(other.s_);
        return *this;
    }
    void Set(std::string s) {
        s_ = std::move(s);
    }
    const std::string& Get() const {
        return s_;
    }
private:
    std::string s_;
};

预期触发Copy Elision的常规场景

该场景触发具名返回值优化(NRVO),直接在ret的内存位置构造foo,避免移动/拷贝:

Foo DoSomething(std::string s) {
    Foo foo{std::move(s)};    
    if (foo.Get() == "2") {
        return foo;
    }
    if (foo.Get() == "4") {
        return foo;
    }
    foo.Set("1");
    return foo;
}

int main()
{
    Foo ret(DoSomething("5"));
}

困惑的场景分析

场景1

Foo DoSomething(std::string s) {
    Foo foo{std::move(s)};    
    if (foo.Get() == "2") {
        return foo;
    }
    if (foo.Get() == "4") {
        return Foo("4");
    }
    foo.Set("1");
    return foo;
}

int main()
{
    Foo ret(DoSomething("5"));
}

结果:调用移动构造函数,未触发Copy Elision。

场景2

Foo DoSomething(std::string s) {
    if (s == "2") {
        return Foo("2");
    }
    if (s == "4") {
        return Foo("4");
    }
    Foo foo{std::move(s)};    
    foo.Set("1");
    return foo;
}

int main()
{
    Foo ret(DoSomething("5"));
}

结果:触发Copy Elision。

场景3

Foo DoSomething(std::string s) {
    Foo foo1{"2"};      
    Foo foo2{"4"};  
    if (s == "2") {
        return foo1;
    }
    if (s == "4") {
        return foo2;
    }
    Foo foo{std::move(s)};
    return foo;    
}

int main()
{
    Foo ret(DoSomething("4"));
}

结果:未触发Copy Elision。

场景4

Foo DoSomething(std::string s) {
    Foo foo1{"2"};      
    Foo foo2{"4"};  
    if (s == "2") {
        return foo1;
    }
    if (s == "4") {
        return foo2;
    }
    Foo foo{std::move(s)};
    return foo;    
}

int main()
{
    Foo ret(DoSomething("5"));
}

结果:触发Copy Elision。

实际触发Copy Elision的核心逻辑

先明确两种核心优化类型:

  • RVO(返回值优化):返回临时对象时,编译器直接在目标位置构造对象,C++17起强制保证,所有场景下只要返回临时对象都会触发。
  • NRVO(具名返回值优化):返回函数内的具名局部变量时,编译器优化掉移动/拷贝。C++20在满足条件时强制触发,实际判断逻辑的关键要求如下:

针对各场景逐一解析:

  1. 场景1未触发:函数存在两种不同返回分支——一个返回具名变量foo,另一个返回临时对象。编译器无法为所有返回路径复用同一内存位置,因此返回foo时只能走移动构造。
  2. 场景2触发:传入"5"时,执行路径仅会走到返回foo的分支。编译器可将foo直接构造在ret的内存位置,从而触发NRVO。
  3. 场景3未触发:传入"4"时返回foo2,但foo1、foo2、foo的生命周期完全重叠,编译器无法将三个变量放在同一返回位置,只能通过移动构造返回foo2。
  4. 场景4触发:传入"5"时返回foo,foo1和foo2虽存在但不会被当前路径返回。编译器可将foo直接构造在ret的内存位置,触发NRVO。

总结NRVO的实际触发条件(C++20):

  • 函数返回路径要么返回同一个具名局部变量,要么返回临时对象(RVO);
  • 返回的具名变量是当前执行路径下唯一的返回对象,且生命周期未被其他返回分支的变量干扰;
  • 具名变量必须是函数内的非静态局部变量,不能是参数或静态变量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:07:35