未按预期调用move构造函数

时间:2019-01-17 16:52:53

标签: c++ c++11

我的自定义实现的Integer类如下:

org.gradle.jvmargs=-XX\:MaxHeapSize\=2048m -Xmx4608M

move构造函数的实现如下:

class Integer
{
private:
    int * ptr_int_; // Resource
public:
    // Other ctors
    Integer(Integer &&); // Move constructor
    // dtor
}

在我的驱动程序中,对于下面的通话,

Integer::Integer(Integer && arg)
{
    std::cout << "Integer(Integer && arg) called\n";
    this->ptr_int_ = arg.ptr_int_; // Shallow Copy
    arg.ptr_int_ = nullptr;
}

我希望有一个参数化的构造函数(用于临时对象),然后移动要调用的构造函数。但是未调用move构造函数。

在拆卸时,我得到了如下所示的东西:

Integer obj2{ Integer{5}}; 

我想这是实际的返回值优化(RVO)。  我说的对吗?

由于大多数编译器都实现了RVO,所以我不应该这样做

Integer obj2{ Integer{5}}; 
001E1B04  push        4  
001E1B06  lea         ecx,[obj2]  
001E1B09  call        Integer::__autoclassinit2 (01E1320h)  
001E1B0E  mov         dword ptr [ebp-114h],5  
001E1B18  lea         eax,[ebp-114h]  
001E1B1E  push        eax  
001E1B1F  lea         ecx,[obj2]  ;; Is this copy elision(RVO) in action?
001E1B22  call        Integer::Integer (01E12FDh)  
001E1B27  mov         byte ptr [ebp-4],1

我应该吗?

2 个答案:

答案 0 :(得分:2)

这是一个棘手的问题,因为它在c ++ 17中进行了技术更改。在c ++ 11中,这是NRVO优化,但是在c ++ 17中,它甚至不再是优化。

  1. 您不应指望移动,这取决于编译器。
  2. 由于c ++ 17您无法期望它,因此无法调用它。

cppreference的相关摘录:

  

在以下情况下,即使复制/移动构造函数和析构函数具有明显的副作用,也要求编译器省略类对象的复制和移动构造。这些对象直接构造到存储中,否则会将它们复制/移动到其中。 复制/移动构造函数不需要存在或不可访问,因为语言规则确保即使在概念上也不会进行复制/移动操作

     
      
  • [...]

  •   
  • 在变量的初始化中,当初始化器表达式是与变量类型相同的类类型(忽略cv限定)的prvalue时:

  •   
     

T x = T(T(f())); // only one call to default constructor of T, to initialize x

     

注意:上面的规则未指定优化:prvalues和temporaries的C ++ 17核心语言规范与早期C ++修订版的根本不同:不再需要复制或移动的临时属性。描述C ++ 17机制的另一种方法是“未实现的值传递”:prvalue的返回和使用无需实现临时值。

重点是我的重点。上段自c ++ 17起就已存在,而在c ++ 11中不存在。

现在,c ++ 11:

  

在以下情况下,允许编译器,但即使复制/移动(自C ++ 11起)构造函数和析构函数也不需要省略类对象的复制和移动(自C ++ 11起)构造有明显的副作用。这些对象直接构造到存储中,否则会将它们复制/移动到其中。 这是一种优化:即使发生并且未调用copy / move(自C ++ 11起)构造函数时,它仍然必须存在并且可以访问(就像没有发生优化一样)完全没有),否则程序格式不正确

     
      
  • [...]

  •   
  • 在对象的初始化中,当源对象是无名的临时对象且与目标对象具有相同的类类型(忽略cv限定)时。当无名临时变量是return语句的操作数时,复制省略的这种变体称为RVO,即“返回值优化”。 (直到c ++ 17)

  •   

这是您的情况。因此对于c ++ 11,它是用于初始化的RVO。 return语句RVO实际上被另一个项目符号覆盖。

答案 1 :(得分:2)

就像您自己想出的那样,由于RVO而没有调用move构造函数。

请注意,std::move只是将其参数转换为右值引用。在您的示例中,Integer{5}是一个未命名的临时变量,已经是一个右值。因此,不需要额外调用std::move。如果未如您所愿地完全删除了move构造函数,那么它将被调用。

另外,请注意,在move构造函数的实现中,多余的std::move是不必要的,因为ptr_int_是原始指针,没有任何特殊的move语义。