C++ 移动构造函数过时了吗?
Is C++ move constructor obsolete?
我写了自己的字符串类型(Str
)来演示基本的构造函数、析构函数和赋值运算符;而且,我可以看到它们都在 C++17 中执行,除了移动构造函数。
显然,由于 Return 值优化 (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)
};
完全没有
Return 值优化并不是使用移动构造函数的唯一点。每次您想从 rvalue
构造某种类型的值时,都会使用移动构造函数。
你基本上把两个问题合二为一。让我们从
开始
Is the move constructor only called in response to explicitly calling 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
。有关详细信息,另请参阅 here
Is it mostly obsolete because of 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
是引用这一事实与是否使用 move 构造无关。这是一个明确的参考,RVO 可能并不总是可能的。在这种情况下,在 some_string
被移动之后,将处于未指定但有效的状态,这是一种奇特的方式,表示不会发生未定义的行为并且引用仍然有效。
进入田野
class FooBar {
string fooField;
//Constructor
FooBar( string bar )
: fooField( ::std::move(bar) ) // Uses move constructor to initialize fooField
{ }
}
我写了自己的字符串类型(Str
)来演示基本的构造函数、析构函数和赋值运算符;而且,我可以看到它们都在 C++17 中执行,除了移动构造函数。
显然,由于 Return 值优化 (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)
};
完全没有
Return 值优化并不是使用移动构造函数的唯一点。每次您想从 rvalue
构造某种类型的值时,都会使用移动构造函数。
你基本上把两个问题合二为一。让我们从
开始Is the move constructor only called in response to explicitly calling 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
。有关详细信息,另请参阅 here
Is it mostly obsolete because of 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
是引用这一事实与是否使用 move 构造无关。这是一个明确的参考,RVO 可能并不总是可能的。在这种情况下,在some_string
被移动之后,将处于未指定但有效的状态,这是一种奇特的方式,表示不会发生未定义的行为并且引用仍然有效。进入田野
class FooBar { string fooField; //Constructor FooBar( string bar ) : fooField( ::std::move(bar) ) // Uses move constructor to initialize fooField { } }