C++移动构造、move、右值引用
1、拷贝构造
用一个临时对象(即将消亡)去初始化、赋值另一对象时,编译器会优先调用移动构造函数。
class Cla_demo{
public:
Cla_demo(const char* str):size(5)
{
num = new char[5];
memcpy(num,str,5);
cout<<"construct!"<<endl;
}
Cla_demo(const Cla_demo &d){
num = new char[5];
memcpy(num,d.num,5);
cout<<"copy construct!"<<endl;
}
~Cla_demo(){
cout <<"num " << static_cast<void *>(num) <<endl;
cout <<this <<endl;
cout<<"class destruct!"<<endl;
}
private:
char *num;
int size;
};
上面的类中无移动构造函数,当main函数如下时:
Cla_demo func(){
return (Cla_demo("abcd"));
}
int main(){
// Cla_demo a(func());
Cla_demo a = func();
cout << "-----------" << endl;
return 0;
}
编译器执行的操作为:
1、在func函数栈上构造一个demo对象 f : Cla_demo(“abcd”)
2、return时执行拷贝构造,将f拷贝到main函数栈中,创建出对象 b,这时会析构f,因为func结束了。
3、此时的b(函数返回值),会再次执行拷贝构造,拷贝给a,然后b被析构。
4、main结束,a被析构。
2 、移动构造
加入移动构造函数:
Cla_demo(Cla_demo &&d){
num = d.num;
d.num = NULL;
cout<<"move construct!"<<endl;
}
此时编译器执行的操作为:
1、在func函数栈上构造一个demo对象 f : Cla_demo(“abcd”)
2、func即将结束时(f即将被析构),执行移动构造,在main函数栈上创建吧,并将f.str的地址空间移动给b.str。
3、 b(函数返回值)是一个右值,因此Cla_demo a(func()) 或 Cla_demo a = func() 会执行移动构造,构造出a,并将b.str的空间移交到a.str。b被析构。
3 、move
std::move会将一个左值转换为右值,可以避免不必要的拷贝。
int main(){
// Cla_demo a(func());
Cla_demo a("abcd"); //1
Cla_demo c(a); //2
Cla_demo d(move(a)); //3
cout << "-----------" << endl;
return 0;
}
1、执行构造函数。
2、执行拷贝构造。
3、执行移动构造。a.str变为空。
增加如下接口:
Cla_demo & operator=(Cla_demo &d)
{
num = d.num;
d.num = NULL;
cout<<"copy = !"<<endl;
return *this;
}
Cla_demo & operator=(Cla_demo &&d)
{
num = d.num;
d.num = NULL;
cout<<"move = !"<<endl;
return *this;
}
然后修改main:
Cla_demo a("abcd");
Cla_demo c;
c = move(a);
会依次执行 有参构造,无参构造,operator=(Cla_demo &&d) 。因此可见,move将a(左值)转化成了右值。
c = func();
上面代码会执行:
有参构造。
移动构造。
operator=(Cla_demo &&d) 。
4、引用折叠forward
万能引用:T &&
规则:
&& & --> &
& && --> &
& & --> &
&& && --> &&
// 完美转发原型:
T&& forward(T&& t) { return static_cast<T&&>(t); }
// 用法: template<typename T>
void func1(T && val) { func2(std::forward<T>(val)); }
// 当传入左值引用
void func1(T& && val) { func2(static_cast<T& &&>(val)); }
// 引用折叠后:
void func1(T& val) { func2(static_cast<T&>(val)); }
// 当传入右值引用
void func1(T&& && val) { func2(static_cast<T&& &&>(val)); }
// 引用折叠后:
void func1(T&& val) { func2(static_cast<T&&>(val)); }
T&& 为形参时,无论传入左值还是右值,后续使用都会退化为左值。如下会出现编译错误:
void RFn(int&& arg){
}
template<typename T>
void ProxyFn(T&& arg){
RFn(arg);
}
void main(){
ProxyFn(1);
}
修改为:
template<typename T>
void ProxyFn(T&& arg){
RFn(std::forward<T>(arg));
}
在万能引用中,编译器有一个规则,如果传入的是左值,则模板类型会被推导成左值引用类型; 传入的是右值,则模板类型就是值的类型。
// FUNCTION TEMPLATE forward
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
如果传入的是左值,则匹配上面的函数模板,传入右值匹配下面的。
左值时_Ty被推导为_Ty&,则static_case<_Ty& &&> 被推导为 static_cast<_Ty&>。
同理,传入右值时_Ty被推导为_Ty&&,则static_case<_Ty&& &&> 被推导为 static_cast<_Ty&&>。
看如下例子:
template<typename T>
void print(T& t){
cout << "lvalue" << endl;
}
template<typename T>
void print(T&& t){
cout << "rvalue" << endl;
}
template<typename T>
void TestForward(T & v){
cout << "TestForward(T & v)" <<endl;
print(v);
print(std::forward<T>(v));
print(std::move(v));
}
template<typename T>
void TestForward(T && v){
cout << "TestForward(T && v)" <<endl;
print(v);
print(std::forward<T>(v));
print(std::move(v));
}
template<typename T>
struct mytype{
T a ;
T b;
};
int main()
{
struct mytype<int> mt{1,2};
TestForward(mt);
TestForward(10);
TestForward(mytype(5));
}
输出为:
TestForward(T & v)
lvalue
rvalue
rvalue
TestForward(T && v)
lvalue
rvalue
rvalue
TestForward(T && v)
lvalue
rvalue
rvalue