首页 > 解决方案 > 未按预期调用移动构造函数

问题描述

我自定义实现的整数类如下:

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;
}

在我的驱动程序中,对于以下调用,

Integer obj2{ Integer{5}}; 

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

在反汇编中,我得到了如下所示的东西:

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

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

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

 Integer obj2{ std::move(Integer{5})}; 

我是不是该?

标签: c++c++11

解决方案


这是一个棘手的问题,因为它在 c++17 中在技术上发生了变化。在 c++11 中它是 NRVO 优化,但在 c++17 中它甚至不再是优化。

  1. 您不应该期望移动 c'tor,这取决于编译器。
  2. 由于 c++17 你不能指望它,它不能被调用。

cppreference的相关摘录:

在以下情况下,编译器必须省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察到的副作用。对象直接构建到存储中,否则它们将被复制/移动到。复制/移动构造函数不需要存在或可访问,因为语言规则确保不会发生复制/移动操作,即使在概念上也是如此

  • [...]

  • 在变量的初始化中,当初始化表达式是与变量类型相同的类类型(忽略 cv-qualification)的纯右值时:

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

注意:上述规则未指定优化:纯右值和临时值的 C++17 核心语言规范与早期 C++ 修订版根本不同:不再有临时复制/移动。描述 C++17 机制的另一种方式是“未实现的值传递”:在没有实现临时值的情况下返回和使用纯右值。

重点是我的重要部分。上述段落自 c++17 以来就已存在,并且在 c++11 中不存在。

现在,c++11:

在以下情况下,编译器允许但不需要省略类对象的复制和移动(C++11 起)构造,即使复制/移动(C++11 起)构造函数和析构函数具有可观察的一面-效果。对象直接构建到存储中,否则它们将被复制/移动到。这是一种优化:即使它发生并且没有调用复制/移动(C++11 起)构造函数,它仍然必须存在且可访问(就好像根本没有发生优化一样),否则程序会出错-形成

  • [...]

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

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


推荐阅读