我编写了自己的字符串类型(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)
};
答案 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
移出后,将处于未指定但有效的状态,这是一种奇妙的方式,可以说不会发生未定义的行为并且引用仍然有效。
移入字段
class FooBar {
string fooField;
//Constructor
FooBar( string bar )
: fooField( ::std::move(bar) ) // Uses move constructor to initialize fooField
{ }
}