C/C++ move/forward/完美转发

本节针对于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后,有如下操作:
在这里插入图片描述

二、Move内部实现:

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可以得知这是一个完美引用;

也就是说,当传入参数为左值,则会推理:

  1. T:Type&;
  2. t:Type&;

因此,内部首先会使用remove_reference来解除引用,之后在右值化,从Type&变为Type再变为Type&&;

通过强行转化,得到右值;

如果传入参数为右值,则会推理:

  1. T:Type;
  2. t:Type&&;

此时后续相当于不执行,还是会返回Type&&,仍然是右值版本;

简而言之,move无论接受左值还是右值,都会将其转化为右值进行回传;

三、Forward内部实现:

forward牵扯到一个完美转发的概念;

何为完美转发,即传入参数类型是什么,后续类型应该是什么;

具体的做法和实现原理原因来自于完美性别推到以及右值引用问题;

1.出现问题的原因:

考虑如下例子:

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后,当且只当传入是右值时,才符合我我们的期望:
此时:

  1. t为Type&&;
  2. T为Type;
    传入forward后,调用第二个形式,返回static_cast(t);

但是注意一下,t为右值引用的左值,通过static_cast变为右值引用;

所以达成目的;

同理,如果按照上述来说,调用create不加上move,采用左值传递,则不会保证左引用性,调用复制构造函数;

因此,按照上述特性,如果希望接管,传递右值,用move,如果简单复制,直接传入左值就行;

使用forward让其结合完美右值引用来进行自主判断,从而达到”完美转发“;

你可能感兴趣的:(C/C++,tips,c++)