复制省略并移动构造函数

Copy elision and move constructor

考虑以下 Person 的定义:

struct Person
{
    Person() { std::cout << "construct, "; }
    Person(const Person&) { std::cout << "copy\n"; }
    Person(Person&&) { std::cout << "move\n"; }
};

和 3 个不同的函数来创建 Person:

Person create1()
{
    std::cout << "create1: ";
    Person p1{};
    return p1;
}

Person create2()
{
    std::cout << "create2: ";
    if constexpr (true)
    {
        Person p1{};
        return p1;
    }
    else
    {
        Person p2{};
        return p2;
    }
}
    
Person create3()
{
    std::cout << "create3: ";
    if constexpr (true)
    {
        return Person{};
    }
    else
    {
        return Person{};
    }
}

最后,我调用创建函数如下:

int main()
{
    Person p1 = create1();
    Person p2 = create2();
    Person p3 = create3();
    return 0;
}

输出为:

create1: construct
create2: construct, move
create3: construct

困扰我的是 create2 的输出。如果在create1create3中没有调用move构造函数,为什么在create2中调用呢?

我正在使用 GCC 12.0.0。

编辑:Clang 13.0.0 不调用移动构造函数。

我认为它是常量表达式中 NRVO 的一个实例 cppreference says:

In constant expression and constant initialization, return value optimization (RVO) is guaranteed, however, named return value optimization (NRVO) is forbidden:

这与CWG 2278有关。

编辑

我重新考虑了:-)它是从main()调用的,所以不能常量初始化。通过实际常量初始化,gcc 和 clang 都不会执行 NRVO。在本例中 (see godbolt):

struct Person {
    bool was_moved;
    constexpr Person() : was_moved{false} { }
    constexpr Person(const Person&) : was_moved{false} { }
    constexpr Person(Person&&) : was_moved{true} { }
};
constexpr Person create() {
    if (true) {
        Person p1;
        return p1;
    }
    else {
        Person p2;
        return p2;
    }
}
constexpr Person p = create();

int main() {
    return p.was_moved;
}

p.was_moved 是 1.

至于为什么 gcc 在 create2() 中不执行 NRVO,一个答案可能是它不是强制性的,根据之前的 cppreference link。另一个可能是 NRVO 的 gcc 实现非常“脆弱”。例如,当只涉及一个命名变量时它起作用:

Person create4() {
    Person p{};
    if  (true) {
        return p;
    }
    else {
        return p;
    }
}

但是当整个东西被放入子范围时停止工作:

Person create5() {
  {
    Person p{};
    if  (true) {
        return p;
    }
    else {
        return p;
    }
  }
}

(see godbolt)

从 2012 年开始就存在关于这两个问题的 gcc 错误:Bug 53637 - NRVO not applied where there are two different variables involved