为什么以下代码不会导致移动对象而不是复制对象?

Why does the following code not result in moving the object instead of copying?

#include <iostream>
#include <time.h>

class A
{
public:
   A() { std::cout << "a ctor\n"; }
   A(const A&) { std::cout << "a copy ctor\n"; }
   A(A&&) { std::cout << "a move ctor\n"; }
};

A f(int i)
{
   A a1;
   return i != 0 ? a1 : A{};
}

int main()
{
   srand(time(0));
   f(rand());
   return 0;
}

输出为:

a ctor

a copy ctor

我希望 f() 中的 a1 将被移动而不是复制。 如果我稍微改变 f() ,它不再是副本而是移动:

A f(int i)
{
   A a1;
   if (i != 0)
   {
      return a1;
   }
   return A{};
}

输出为:

a ctor

a move ctor

你能解释一下这是如何工作的吗? 海湾合作委员会 9.3.0

(编辑:添加随机数以防止 RVO)

您看到的差异是由于使用了 ternary/conditional 运算符。三元运算符为其第二个和第三个操作数确定 公共类型 值类别 ,这是在编译时确定的。参见 here

你的情况:

return i != 0 ? a1 : A{};

公共类型是 A,公共值类别是纯右值,因为 A{} 是纯右值。但是,a1 是一个左值,在左值到右值的转换中必须制作它的纯右值临时副本。这解释了为什么您会看到在条件为真时调用复制构造函数:创建 a1 的副本以将其转换为纯右值。纯右值被您的编译器复制。

在第二个示例中,您有一个 if 语句,这些规则不适用于三元运算符的情况。所以这里没有调用复制构造函数。


为了解决您对第二个和第三个操作数具有左值的条件语句的评论,根据复制省略的rules,如果:

In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".

这样的条件语句
return i != 0 ? a1 : a1;

其中第二个和第三个操作数是左值,不满足此条件。该表达式是一个条件,而不是一个自动对象的名称。因此没有复制省略和复制构造函数被调用。