使用 if 语句移动构造函数,但使用三元运算符复制构造函数

Move constructor with if-statement but copy constructor with ternary operator

上下文: 我正在做实验以了解 gcc 何时执行 RVO,如果不执行,它何时使用移动语义。我的 gcc 版本是 g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4).

问题: 我有一个函数 return 是 Foo 的值。编译器无法执行 RVO,因为有两个可能的命名 return 值。当我使用三元运算符 select Foo 到 return 中的哪个时,我需要显式调用 std::move 以避免复制。使用 if 语句时,我不需要 std::move。为什么会出现差异?

代码:

#include <iostream>

using namespace std;

struct Foo {
    std::string s;
    Foo()                                        { cout << "Foo()\n"; }
    ~Foo()                                       { cout << "~Foo()\n"; }
    Foo(const Foo& other)     : s(other.s)       { cout << "Foo(const Foo&)\n"; }
    Foo(Foo&& other) noexcept : s(move(other.s)) { cout << "Foo(Foo&&)\n"; }
};

Foo makeFooIf(bool which) {
    Foo foo1; foo1.s = "Hello, World1!";
    Foo foo2; foo2.s = "Hello, World2!";
    if (which) return foo1;
    else       return foo2;
}

Foo makeFooTernary(bool which) {
    Foo foo1; foo1.s = "Hello, World1!";
    Foo foo2; foo2.s = "Hello, World2!";
    return which ? foo1 : foo2;
}

Foo makeFooTernaryMove(bool which) {
    Foo foo1; foo1.s = "Hello, World1!";
    Foo foo2; foo2.s = "Hello, World2!";
    return which ? move(foo1) : move(foo2);
}

int main()
{
    cout << "----- makeFooIf -----\n";
    Foo fooIf = makeFooIf(true);
    cout << fooIf.s << endl;

    cout << "\n----- makeFooTernary -----\n";
    Foo fooTernary = makeFooTernary(true);
    cout << fooTernary.s << endl;

    cout << "\n----- makeFooTernaryMove -----\n";
    Foo fooTernaryMove = makeFooTernaryMove(true);
    cout << fooTernaryMove.s << endl;

    cout << "\n----- Cleanup -----\n";
    return 0;
}

输出:

----- makeFooIf -----
Foo()
Foo()
Foo(Foo&&)
~Foo()
~Foo()
Hello, World1!

----- makeFooTernary -----
Foo()
Foo()
Foo(const Foo&)
~Foo()
~Foo()
Hello, World1!

----- makeFooTernaryMove -----
Foo()
Foo()
Foo(Foo&&)
~Foo()
~Foo()
Hello, World1!

----- Cleanup -----
~Foo()
~Foo()
~Foo()

在某些情况下有隐含着法:

§12.8.32

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

我的加粗