当写在一行中时,C++ 移动构造函数不使用复合运算符 += 调用

C++ Move constructor not called with the compound operator += when written in one line

我遵循了 Whosebug 中有关 Move 和 Operator 重载的精彩教程(例如 What are the basic rules and idioms for operator overloading?),但以下情况让我感到困惑。代码中没什么特别的,只是在调用特殊成员函数时打印。

主要代码:

    class B {
public:
    B() { std::cout << "B::ctor\n"; }

    ~B() { std::cout << "B::dtor\n"; }

    B(B const &b) {
        std::cout << "B::copy ctor\n";
    }

    B &operator=(B const &rhs) {
        std::cout << "B::copy assignment\n";
        return *this;
    }

    B(B &&b) {
        std::cout << "B::move ctor\n";
    }

    B &operator=(B &&rhs) {
        std::cout << "B::move assignment\n";
        return *this;
    }

    B &operator+=(B const &rhs) {
        std::cout << "B::operator+=\n";
        return *this;
    }
};



int main() {
  B b;
  std::cout << "=== b = b + b + b ===\n";
  b = b + b + b;
}

现在,两种情况,我分别以不同方式定义运算符 +:

B operator+(B p1, B const &p2) {
    std::cout << "B::operator+\n";
    return p1 += p2;
}

整个程序的输出:

B::ctor
=== b = b + b + b ===
B::copy ctor
B::operator+
B::operator+=
B::copy ctor
B::operator+
B::operator+=
B::copy ctor
B::move assignment
B::dtor
B::dtor
B::dtor
B::dtor

和第二种情况:

B operator+(B p1, B const &p2) {
    std::cout << "B::operator+\n";
    p1 += p2;
    return p1;
}

输出:

B::ctor
=== b = b + b + b ===
B::copy ctor
B::operator+
B::operator+=
B::move ctor
B::operator+
B::operator+=
B::move ctor
B::move assignment
B::dtor
B::dtor
B::dtor
B::dtor

为什么第二种情况确实给出了预期的结果,正确使用了移动语义,但第一种情况却到处复制?

我只想补充一点,第二个场景是我阅读的教程中推荐的场景(如上面的 link),但是当我尝试实现它时,我凭直觉写了第一个场景并且它给了我错误的行为...

从具有相同1 return 类型 T is a special case 的函数返回类型 T 的局部变量.

它至少会自动移动变量,或者,如果编译器足够聪明,可以执行 so-called NRVO,则完全消除 copy/move 并直接在正确的位置构造变量。

函数参数(与常规局部变量不同)不符合 NRVO 的条件,因此您总是在 (2) 中得到隐式移动。

这不会发生在 (1) 中。编译器不会分析 += 来理解它 return 的内容;此规则仅在 return 的操作数是单个变量时有效。

由于 += return 是一个左值引用,而您没有 std::move 它,因此调用了复制构造函数。


1 或仅 cv-qualifiers.

不同的类型