编译器生成的移动构造函数的行为是什么?

时间:2013-04-17 08:34:25

标签: c++ c++11 move-semantics

std::is_move_constructible<T>::value == true是否意味着T有一个可用的移动构造函数? 如果是这样,它的默认行为是什么?

考虑以下情况:

struct foo {
    int* ptr;
};

int main() {
    {       
        std::cout << std::is_move_constructible<foo>::value << '\n';
        foo f;
        f.ptr = (int*)12;
        foo f2(std::move(f));
        std::cout << f.ptr << ' ' << f2.ptr << '\n';
    }
    return 0;
}

,输出为:

1
0000000C 0000000C

我认为f.ptr应为nullptr。 所以在这种情况下,

  1. f2移动构建了吗?
  2. 如果是,那么左值是否应该无效?
  3. 如何知道某个类的实例是否可以正确移动构造(使旧的实例无效)?
  4. (我正在使用VS11。)

    更新

    移动构造函数的默认行为与复制构造函数相同,是否正确? 如果是的话,

    1. 我们总是希望移动ctor来窃取移动对象的资源,而默认移动对象的行为并不像预期的那样,那么默认移动ctor有什么意义呢?
    2. 我如何知道某个类是否有自定义移动构造函数(可以保证其行为正常)?
    3. 当我宣布一个时,foo f2(std::move(f));似乎调用了复制文件,参见:

      struct foo {
          int* ptr;
          foo() {}
          foo(const foo& other) {
              std::cout << "copy constructed\n";
          }
      };
      
      int main() {
          {       
              std::cout << std::is_move_constructible<foo>::value << '\n';
              foo f;
              foo f2(std::move(f));
          }
          system("pause");
          return 0;
      }
      

      现在输出是:

      1
      copy constructed
      

      如果foo有移动构造函数,那么foo f2(std::move(f))会不会调用它吗?

      所以现在我的问题是: 如何知道一个类是否有移动ctor,如果有一个移动ctor,我该如何明确地调用它?

      我要做的是......

      template<typename T, bool has_move_ctor>
      struct MoveAux;
      
      template<typename T>
      struct MoveAux<T, true> {
          static void doMove(T* dest, T* src) {
              new(dest) T(std::move(*src)); //move ctor
          }
      };
      
      template<typename T>
      struct MoveAux<T, false> {
          static void doMove(T* dest, T* src) {
              new(dest) T(*src); //copy ctor
              src->~T();
          }
      };
      
      template<typename T>
      inline doMove(T* dest, T* src) {
          MoveAux<T,/*a trait*/>::doMove(dest, src);
      }
      

      所以我认为std::is_move_constructible<T>::value可以传递给模板,而现在我发现这个特性只关心T t(T())是否是一个有效的表达式,它可能会调用T::T(const T&)。 现在假设T是一个自定义类,那么我希望上面的模板行为如下:

      1. 如果我没有声明移动ctor,我希望该模板方法调用MoveAux<T,false>::doMove
      2. 如果我宣布一个,我需要拨打MoveAux<T,true>::doMove
      3. 是否可以使这项工作?

6 个答案:

答案 0 :(得分:21)

  

std::is_move_constructible<T>::value == true是否暗示T有一个可用的移动构造函数?

移动构造函数或复制构造函数。请记住,复制结构的操作满足操作移动构造的所有要求,还有一些。

在标准术语中,MoveConstructible对象是对表达式进行评估的对象:

T u = rv; 

在构造之前使u等同于rv的值;移动之后rv 的状态为未指定。但由于它未指定,这意味着状态甚至可能与移动之前的 rv相同:换句话说,u可能是复制rv

事实上,标准将CopyConstructible概念定义为MoveConstructible概念的细化(因此CopyConstructible的所有内容也是{{1}但是,反之亦然)。

  

如果是这样,它的默认行为是什么?

隐式生成的移动构造函数的行为是对生成它的类型的数据成员执行成员移动。

C ++ 11标准的第12.8 / 15节:

  

非联合类MoveConstructible的隐式定义的复制/移动构造函数执行成员复制/移动   其基地和成员。 [注意:忽略非静态数据成员的 brace-or-equal-initializers 。看到   也是12.6.2中的例子。 - 后注]

此外:

  

1 - X移动构建了吗?

  

2 - 如果是这样,rval值不应该失效吗?

移动指针与复制它相同。所以没有失效,也不应该继续。如果你想要一个移动构造函数,将移动的对象保留在特定的状态(即将指针数据成员设置为f2),你必须自己编写 - 或者将这个职责委托给某个智能指针类,如nullptr

请注意,“ invalidated ”这个词在这里并不完全正确。移动构造函数(以及移动赋值运算符)意味着将移动的对象保留为有效(尚未指定)状态。

换句话说,需要尊重类不变量 - 并且应该可以调用移动的对象操作,这些操作对其状态没有任何先决条件(通常是破坏和赋值)。

答案 1 :(得分:5)

  

std :: is_move_constructible :: value == true表示T有一个可用的移动构造函数吗?

没有。它声明你可以take an rvalue expression of the object type and construct an object from it。这是使用移动构造函数还是复制构造函数与此特征无关。

  

是f2移动构建的吗?

  

如果是的话,rval值不应该失效吗?

没有。这不是运动的运作方式。

  

如何知道类的实例是否可以正确移动构造(使旧的实例无效)?

这不是存在的“正确移动构造”的任何定义。如果你想“使旧的无效”,那么你必须自己做。

移动构造通常保证 nothing 关于旧对象的状态。它将处于有效但未定义的状态。这种状态非常可以“与以前一样”。移动指针的构造与复制指针相同。

如果你想在移动后“无效”,那么你需要编写你自己的移动构造函数,明确地这样做。

  

(我正在使用VS11)

然后你根本没有编译器生成的移动构造函数。并不重要,因为指针的移动和复制构造函数都做同样的事情。

答案 2 :(得分:3)

  

移动构造函数的默认行为与副本相同   构造函数,这是正确的吗?如果这是真的

没有。这是不对的。它仅适用于原始人。它类似于复制构造函数。

默认生成的复制构造函数以声明的顺序调用其所有成员的复制构造函数

但默认生成的移动构造函数以声明的顺序调用其所有成员的移动构造函数

现在接下来的问题是原语int s float s pointer的复制/移动构造函数是什么?

Ans:他们只是复制值(复制和移动构造函数)

答案 3 :(得分:2)

请注意,Visual Studio 2012 / VC ++ 11 支持编译器生成的移动构造函数;事实上,请考虑"C++11 Features in Visual C++ 11"博客文章(强调我的)中的这句话:

  

Rvalue references v3.0添加了新规则以自动生成移动   构造函数和在特定条件下移动赋值运算符。   此将不会在VC11中实施,这将继续遵循   VC10的行为从不自动生成移动   构造函数/移动赋值运算符

使用原始指针,您必须自己定义移动构造函数,手动清除旧的“移动指针”:

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(other.m_ptr) // copy pointer value
    {
        // Clear out old "moved-from" pointer, to avoid dangling references
        other.m_ptr = nullptr;
    }

private:
    int* m_ptr;
};

相反,如果您使用智能指针,例如std::unique_ptr,则会正确定义移动构造函数,您只需调用std::move

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(std::move(other.m_ptr)) // move from other, 
                                        // old pointer automatically cleared
    {
    }

private:
    std::unique_ptr<int> m_ptr;
};

使用自动生成的移动构造函数,如果成员方式移动对您没有问题,则不必显式定义自定义移动构造函数。

答案 4 :(得分:0)

n3376 12.8 / 15

非联合类X的隐式定义副本/ 移动构造函数执行成员复制/ 移动 其基地和成员。

每个基础或非静态数据 成员以适合其类型的方式复制/移动:

- 如果成员是一个数组,则每个元素都使用x的相应子对象进行直接初始化;

- 如果成员m具有右值参考类型T&amp;&amp;,则使用static_cast(x.m)进行直接初始化;

- 否则,使用x的相应基数或成员对基数或成员进行直接初始化。

答案 5 :(得分:0)

  

如果foo有一个移动构造函数,那么不会foo f2(std :: move(f))调用它吗?   提供复制构造函数时,不会获得默认的移动构造函数。添加以下行以获取它(并注意更改)。   foo(foo&amp;&amp; ifm)=默认;