C++移动语义和完美转发

移动语义和完美转发都是通过C++的右值引用来实现的。关于什么是右值引用可以去看看C++ primer这本书。

1、移动语义

首先,很多人会想,为什么我们有了拷贝赋值以后为什么还要有移动语义呢。我们先来看一个例子,假设我们有个类A和类B(两个类都是class test类型,见下图

),假如我们想把类A赋值给类B(类A中有通过new分配的内存空间),传统的赋值拷贝是要再申请一块内存空间给B,然后再把A的那块动态分配空间中的内容拷贝到B的那块空间(见下图)。但是现在我们考虑一种情况,这种情况就是我把A的值赋给B之后我就不会再使用A了,这时候如果再执行深拷贝是不是有点浪费了。这时候移动语义就发挥作用了,我就把A的指针给B不就行了吗(相当于一个浅拷贝),这样比深拷贝快了很多。

class test{

public:
    test& operator=(const test &A){
        分配一个新的空间;
        用memcpy拷贝A中的内容;
    
    }
    test& operator=(test &&A){
        B指针 = A指针;
        A指针置为空;
    }

private:
    指针;

};

 

2.完美转发

为什么需要完美转发这种东西呢,让我们先来看一下。首先,我们有个函数A,里面再调用函数B,这种函数调用很常见是吧。函数调用见下面的图。当我们传入一个右值引用参数到函数A的时候,我们再把参数a传入函数B会调用B的那个版本的函数呢,让我们来看一下。

#include<iostream>
using namespace std;
class test {
public:
	int a;
};
void B(const test &a) {
	cout << "B &" << endl;
}
void B(const test &&a) {
	cout << "B &&" << endl;
}
void A(const test &&a) {
	B(a);
}
int main() {

	test a;
	A(std::move(a));
	return 0;

}
B &

由上面的结果可见并不是我们所想象的调用B &&版本,为什么会这样呢,因为虽然test &&a代表一个右值引用,但是a是一个左值,所以就会调用B &版本的函数。那么如果我们想调用B &&版本呢,我们可以怎么做。看下面代码。

#include<iostream>
using namespace std;
class test {
public:
	int a;
};
template<typename m>
void B(const m &a) {
	cout << "B &" << endl;
}

template<typename m>
void B(const m &&a) {
	cout << "B &&" << endl;
}

template<typename m>
void A(m &&a) {
	B(std::forward<m>(a));
}
int main() {

	test a;
	A(std::move(a));
	A(a);
	return 0;

}

B &&
B &

看,加上一个forward就可以实现了,是不是很神奇,那么我们接下来看看move函数和forward函数做了些什么呢。

 

3.引用折叠

在看实现之前我们还需要了解一下引用折叠这个东西。一共有四种引用折叠

X&   &, X&  &&, X&&  &都折叠成类型X &

X&&  &&折叠成X &&

让我们看几个例子

template<typename X>
void fun(X &&a){

    cout<<"aa"<<endl;
}



1.
class test a;
fun(a)  //这时候X的类型为test &
fun(std::move(a))  //这时候X的类型为test &&(其实按引用折叠的规则来看X也可以是test,但是我在下面测试的结果表明X为test &&)

 

4.std::move

move的作用是把参数转换成右值引用。我们看下它是怎么实现的呢。

template<class T>
typename remove_reference<T>::type&&
std::move(T&& a) noexcept
{
  typedef typename remove_reference<T>::type&& RvalRef;
  return static_cast<RvalRef>(a);
}


以下为remove_reference可能的实现
template< class T > struct remove_reference      {typedef T type;};
template< class T > struct remove_reference<T&>  {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};

首先让我们来看看remove_reference是干什么用的呢,可以看到它的作用其实就是把引用去掉,不管是左值引用还是右值引用。

然后再让我们看看move的实现,首先我们可以看见move还是的返回类型是一个右值引用,函数的实现很简单就是使用static_cast把参数a转换为右值引用类型。

 

5.forward

template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
{
  return static_cast<S&&>(a);
}

然我们结合上个例子来看forward

#include<iostream>
using namespace std;
class test {
public:
	int a;
};
template<typename m>
void B(const m &a) {
	cout << "B &" << endl;
}

template<typename m>
void B(const m &&a) {
	cout << "B &&" << endl;
}

template<typename m>
void A(m &&a) {
	B(std::forward<m>(a));
}
int main() {

	test a;
	A(std::move(a));
	A(a);
	return 0;

}

当A的传入参数std::move(a)时,m为test &&(这时候X的类型为test &&(其实按引用折叠的规则来看X也可以是test,但是我在下面测试的结果表明X为test &&)类型,也就是forward中的S为test &&类型,当S为test &&类型时可见forward返回一个      test&&  &&即test &&类型,可见传入函数A和函数B的参数类型编程一致了。

当A的传入参数为a时,m为test &类型,也就是forward中的S为test &类型,这时候返回类型为S &&即为test& &&,按照引用折叠的规则,返回forward的返回类型为test &,也就是B中的参数为test &,和传入A的参数类型一致。

 

 

6.测试:

#include<iostream>
using namespace std;
class test {
public:
	test(int c):a(c){}
	int a;
};
template<typename m>
void B(const m &a) {
	cout << "B &" << endl;
}

template<typename m>
void B(const m &&a) {
	cout << "B &&" << endl;
}

template<typename m>
void A(m &&a) {
	test b(2);
	B(static_cast<m>(b));
	
}
int main() {

	test a(1);
	A(std::move(a));
	
	while (1);
	return 0;

}
B &&

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值