本节针对于std::move,std::forward,左右引用做一个基本得归纳和整理;
之所以需要说白了还是在构造对象的性能优化上;
针对于构造方式,无非是:深拷贝,浅拷贝 两种;
所以一般而言,对象内部应该实现两种构造函数,移动构造函数和拷贝构造函数;
前者用于直接转让内部对象,避免复制耗时,而后者主要是直接进行全量复制;
例如下例:
class A {
public:
A() {
cout << "construct.." << endl;
ptr = new int(100);
}
~A() {
cout << "deconstruct" << endl;
if (ptr)
delete ptr;
}
A& operator=(const A& a) {
cout << "operator =" << endl;
return *this;
}
A(const A& a) {
cout << "copy construct.." << endl;
ptr = new int();
memcpy(ptr, a.ptr, sizeof(int));
}
A(A&& a) {
std::cout << "A move construct ..." << std::endl;
ptr = a.ptr;
a.ptr = nullptr;
}
private:
int* ptr;
};
可以看到复制构造函数一般接受的是一个左值,而移动构造函数一般接受一个右值;
主要原因是,右值会随时毁灭,于其浪费空间做副本,不如直接接管;
但是对于某些情况下,可能左值也有对应的操作需求;
例如某个左值对象后续可能不需要使用,作为遗留的状态进行保留,此时,入队列如果采用push_back则会优先进行复制构造函数,这对于性能是很大的浪费;
因此,move的需求应运而生;
move可以直接将左值转换为右值,目的说白了就是调用移动构造函数而非复制构造函数;
例如:
int main() {
vector<A> vec;
auto a = new A();
vec.push_back(move(*a));
system("pause");
return 0;
}
move源码如下所示:
template<typename T>
constexpr typename std::remove_reference<T>::type&& move(T&& t){
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
可以看到,该模板接受一个右值引用,根据Modern C++的条款1-5可以得知这是一个完美引用;
也就是说,当传入参数为左值,则会推理:
因此,内部首先会使用remove_reference
来解除引用,之后在右值化,从Type&变为Type再变为Type&&;
通过强行转化,得到右值;
如果传入参数为右值,则会推理:
此时后续相当于不执行,还是会返回Type&&,仍然是右值版本;
简而言之,move无论接受左值还是右值,都会将其转化为右值进行回传;
forward牵扯到一个完美转发的概念;
何为完美转发,即传入参数类型是什么,后续类型应该是什么;
具体的做法和实现原理原因来自于完美性别推到以及右值引用问题;
考虑如下例子:
template<typename T>
A* Create(T&& t){
return new A(t);
}
create模板函数接受一个完美右值引用,并且通过该值构建A;
这里如果当t为右值,我们期望按照右值的移动构造来生成返回对象A;
按照通用的想法:
A* a=A();
A* b = Create(std::move(a));
传入move(a),应该可以达到预期;
实际不然,这里牵扯一个右值引用问题;
在create中,虽然t是一个右值引用,引用一个右值,但是右值引用算作一个左值;
因此调用的实际上还是左值参数;
因此,这就需要forward;
template<typename T>
A* Create(T&& t){
return new A(std::forward<T>(t));
}
forward内部实现如下所示:
template<typename T>
constexpr T&& forward(typename std::remove_reference<T>::type& t)noexcept
{
return static_cast<T&&>(t);
}
template<typename T>
constexpr T&& forward(typename std::remove_reference<T>::type&& t)noexcept
{
return static_cast<T&&>(t);
}
当调用Create后,当且只当传入是右值时,才符合我我们的期望:
此时:
但是注意一下,t为右值引用的左值,通过static_cast变为右值引用;
所以达成目的;
同理,如果按照上述来说,调用create不加上move,采用左值传递,则不会保证左引用性,调用复制构造函数;
因此,按照上述特性,如果希望接管,传递右值,用move,如果简单复制,直接传入左值就行;
使用forward让其结合完美右值引用来进行自主判断,从而达到”完美转发“;