C ++ move构造函数过时了吗?

时间:2018-08-22 03:14:44

标签: c++ move-semantics

我编写了自己的字符串类型(Str),以演示基本的构造函数,析构函数和赋值运算符;而且,除了移动构造函数外,我都能看到它们全部在C ++ 17中执行。

显然,由于返回值优化(RVO),不再使用move构造函数。

仅在显式调用std::move时才调用move构造函数吗?

什么时候还能叫它?
是否大多数由于RVO而过时了?

这是我的Str类型:

struct Str {
  Str(): p(nullptr) {}
  Str(const char* s) { cout << "cvctor \"" << s << "\"\n"; copy(s); }
  Str(const Str& s): p(nullptr) { cout << "cpctor deep\""<<s.p<<"\"\n"; copy(s.p); }
  Str( Str&& s) { cout << "mvctr shallow \"" << s.p << "\"\n"; p = s.p; s.p=nullptr; }
  const Str& operator=(const Str& s) { cout << "op=\""<<s.p<<"\"\n"; copy(s.p); return *this; }
  const Str& operator=( Str&& s) { cout << "op= shallow \""<<s.p<<"\"\n"; p=s.p; s.p=nullptr; return *this; }
  ~Str(){ if ( p != nullptr ) { cout << "dtor \"" << p << "\"\n"; delete [] p; } }

private:
  char* p = nullptr;
  char* copy(const char* s)
};

1 个答案:

答案 0 :(得分:3)

完全没有

返回值优化不是使用move构造函数的唯一方法。每次您想要从rvalue构造某种类型的值时,都会使用move构造函数。

您基本上会问两个问题。让我们开始

  

仅在响应显式调用std :: move时才调用move构造函数吗?

move构造函数和std::move切线相关,但本质上是非常分开的。每当您从相同类型的rvalue初始化变量时,都会调用move构造函数。另一方面,std::move可以从所谓的lvalue显式地变成这样的rvalue,但这不是唯一的方法。

template<typename T>
void foo(T&& value) { // A so-called universal reference
    T other_value = std::forward<T>(value);
}
foo( string{"some string"} ); // other_value is move initialized

您看到,std::forward是获取rvalue的另一种方法。实际上,"some string"还会在上面的代码中产生rvalue类型的const char*


间奏的时间。如果您听到rvalue,您可能会想起&&这就是rvalue-reference的想法。这是微妙的不同。问题在于给任何名称起一个名字就使其成为lvalue。所以下面的代码:

foo(string&& value) {
    T other_value = value;
}
foo( "some_string" ); // other_value is STILL copy initialized

foo(string&& value) {
    T other_value = std::move(value);
}
foo( "some_string" ); // other_value is now properly move initialized

思考&&的正确方法是可以使用rvalue初始化这样的引用,但是它本身并不总是这样的rvalue。有关更多信息,请参见here


  

是否由于RVO而已过时?

想到两个值得注意的示例,其中除了RVO之外,还经常使用move构造函数

  • 移入方法参数

    void foo(string value);
    // Somewhere else
    string& some_string = get_me_a_string();
    foo( ::std::move(some_string) ); // Uses move constructor to initialize value
    some_string.clear(); // Most probably a no-op
                        // Doing this leaves an empty some_string
    

    请注意,在上面的示例中,some_string是引用的事实与是否使用移动构造无关紧要。明确指出RVO并非总是可能的。在这种情况下,some_string移出后,将处于未指定但有效的状态,这是一种奇妙的方式,可以说不会发生未定义的行为并且引用仍然有效。

    < / li>
  • 移入字段

    class FooBar {
        string fooField;
        //Constructor
        FooBar( string bar )
        : fooField( ::std::move(bar) ) // Uses move constructor to initialize fooField
        { }
    }