移动构造函数是否C++过时?

Is C++ move constructor obsolete?

本文关键字:过时 C++ 是否 构造函数 移动      更新时间:2023-10-16

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

显然,由于返回值优化 (RVO(,移动构造函数不再被大量使用。

移动构造函数是否仅在响应显式调用std::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)
};

不,一点也不

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

你基本上会问两个问题。让我们从

移动构造函数是否仅在响应显式调用 std::move 时才调用?

移动构造函数和std::move是切线相关的,但本质上是非常独立的。每次从相同类型的rvalue初始化变量时,都会调用移动构造函数。另一方面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。有关更多信息,另请参阅此处


它是否因为 RVO 而大部分已经过时?

除了 RVO 之外,还经常使用移动构造函数的两个值得注意的例子

  • 进入方法参数

    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被移出后,将保持未指定但有效的状态,这是一种奇特的方式,表示不会发生未定义的行为并且引用仍然有效。

  • 移入田间

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