美文网首页
右值引用

右值引用

作者: Magic11 | 来源:发表于2019-12-16 10:49 被阅读0次
一、右值引用主要用来解决C++ 98/03中遇到的两个问题:

1、 临时对象非必要的昂贵的拷贝操作
  1)临时对象来构造一个新的对象,调用拷贝构造函数,需要创建一个和临时对象一样的堆,并释放临时对象所指向的堆。
  2)需要引入移动语义——移动构造函数和移动赋值构造函数,它可以让新对象的堆直接指向临时对象的堆,从而省去一次创建堆和释放堆的操作。
  3) 为了使左值能够使用移动语义,需要引入move——强制将左值转化为右值,从而能够使用移动语义(只有右值才能使用移动构造函数)。

2、 在模板函数中如何按照参数的实际类型进行转发
  当在函数模板中,调用另一个函数并将函数模板接收到的参数传给它时,如果函数模板接收的参数是右值,此时在另一个函数中接收到的参数就变成了左值,为了保证另一个函数接收到的依然是右值,此时引入完美转发——std::forward。

二、右值引用相关的概念:

  右值、纯右值、将亡值、universal references(未定的引用类型)、引用折叠、移动语义、move语义和完美转发等等
右值包含纯右值(字面值常量)和将亡值(临时对象),

区分左值、右值的方法:

  看能不能对表达式取地址,能取地址就是左值,不能取地址就是右值。

  所有的具名变量都是左值,而匿名变量则是右值。

  C++11中,所有的值必须是左值、将亡值、纯右值三者之一。

纯右值:

  非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式

将亡值:

  C++11新增的、与右值引用相关的表达式:将要被移动的对象、T&&函数返回值和转换为T&&类型的转换函数的返回值

引用折叠的规则:

  1)所有的右值引用叠加到右值引用仍然还是一个右值引用
  2)所有的其他引用类型之间的叠加都将变成左值引用

A& & 折叠成 A&
A& && 折叠成 A&
A&& & 折叠成 A&
A&& && 折叠成 A&&

右值引用的第一个特点:

  通过右值引用的声明,右值又“重获新生”,其生命周期和右值引用类型变量的生命周期一样长,只要该变量还活着,该右值临时量将会一直存活下去。

右值引用的第二个特点:

  右值引用独立于左值和右值,即右值引用类型的变量可能是左值,也可能是右值。

右值引用的第三个特点:

  T&& t在发生自动类型推断的时候,它是未定的引用类型(universal references),如果被一个左值初始化,它就是一个左值,如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。
代码示例如下:

template <typename T>
void fun(T&& t)
{

}

int main() {

    fun(10);  //编译器自动推导成   void fun<int>(int&& t),此时T就推导成int类型

    int val = 11;
    fun(val); //编译器自动推导成   void fun<int&>(int& t),此时T就推导成int&类型
}

初始化参数为右值时,自动推导为右值

初始化为右值

初始化参数为左值时,自动推导为左值


初始化为左值

  正是因为右值引用可能是左值也可能是右值,依赖于初始化,并不是一下子就确定的特点,我们可以利用这一点做很多文章,比如后面介绍的移动语义和完美转发。

三、完美转发

  右值引用T&&是一个universal references,可以接受左值或者右值,正是这个特性让他适合作为一个参数的路由,然后再通过std::forward按照参数的实际类型去匹配对应的重载函数,最终实现完美转发。

完美转发的一个示例:

template <typename T>
void processVal(T &val)
{
    cout << "左值" << endl;
}

template <typename T>
void processVal(T&& val)
{
    cout << "右值" << endl;
}
//不使用完美转发
template <typename T>
void func(T&& val)
{
    processVal(val);
}
//使用完美转发
template <typename T>
void funcForward(T&& val)
{
    processVal(std::forward<T>(val));
}

int main()
{
    //不使用完美转发
    func(10);        //参数变为右值

    int val = 11;
    func(val);       //参数保持左值

    //使用完美转发
    funcForward(10); //参数保持右值

    funcForward(val);//参数保持左值

    getchar();

    return 0;
}

运行结果


image.png
四、move、forward和swap
1、move的代码实现:
template <typename T>
typename remove_reference<T>::type&& move(T&& param)
{
    using ReturnType = typename remove_reference<T>::type&&;

    return static_cast<ReturnType>(param);
}

remove_reference<T>::type是T取出引用之后的类型,所以remove_reference<T>::type&&一定是右值引用,
remove_reference<T>::type&一定是左值引用。

T&& param存在于模板推导中,是一个未定义的引用类型,如果实参是左值,则推导为左值引用,如果实参是右值,则推导为右值引用

2、swap代码实现:

c++11之前swap的实现:

template <typename T>
void swap(T& a, T& b)
{
    T tmp{a};    // 调用复制构造函数
    a = b;       // 复制赋值运算符
    b = tmp;     // 复制赋值运算符
}

c++11中swap的实现:

template <typename T>
void swap(T& a, T& b)
{
    T temp{std::move(a)};   // 调用移动构造函数
    a = std::move(b);       // 调用移动赋值运算符
    b = std::move(tmp);     // 调用移动赋值运算符
}
3、forward的代码实现:
template<typename T> 
T&& forward(typename remove_reference<T>::type& param) 
{
    return static_cast<T&&>(param);
}

下面是remove_reference的实现:

template <class T>
struct remove_reference {
    using type = T;
};

// 特化版本
template <class T>
struct remove_reference<T&> {
    using type = T;
};

template <class T>
struct remove_reference<T&&> {
    using type = T;
};

参考:https://zhuanlan.zhihu.com/p/54893850
https://zhuanlan.zhihu.com/p/54050093
再看一个动态数组的示例:

template <typename T>
class DynamicArray
{
public:
    explicit DynamicArray(int size) :
        m_size{ size }, m_array{ new T[size] }
    {
        cout << "Constructor: dynamic array is created!\n";
    }

    virtual ~DynamicArray()
    {
        delete[] m_array;
        cout << "Destructor: dynamic array is destroyed!\n";
    }

    // 复制构造函数
    DynamicArray(const DynamicArray& rhs) :
        m_size{ rhs.m_size }
    {

        m_array = new T[m_size];
        for (int i = 0; i < m_size; ++i)
            m_array[i] = rhs.m_array[i];
        cout << "Copy constructor: dynamic array is created!\n";
    }

    // 复制赋值操作符
    DynamicArray& operator=(const DynamicArray& rhs)
    {
        cout << "Copy assignment operator is called\n";
        if (this == &rhs)
            return *this;

        delete[] m_array;

        m_size = rhs.m_size;
        m_array = new T[m_size];
        for (int i = 0; i < m_size; ++i)
            m_array[i] = rhs.m_array[i];

        return *this;
    }

    // 移动构造函数
    DynamicArray(DynamicArray&& rhs) :
        m_size{ rhs.m_size }, m_array{ rhs.m_array }
    {
        rhs.m_size = 0;
        rhs.m_array = nullptr;
        cout << "Move constructor: dynamic array is moved!\n";
    }

    // 移动赋值操作符
    DynamicArray& operator=(DynamicArray&& rhs)
    {
        cout << "Move assignment operator is called\n";
        if (this == &rhs)
            return *this;
        delete[] m_array;
        m_size = rhs.m_size;
        m_array = rhs.m_array;
        rhs.m_size = 0;
        rhs.m_array = nullptr;

        return *this;
    }


    // 索引运算符
    T& operator[](int index)
    {
        // 不进行边界检查
        return m_array[index];
    }

    const T& operator[](int index) const
    {
        return m_array[index];
    }

    int size() const { return m_size; }
private:
    T* m_array;
    int m_size;
};
yun

// 生产int动态数组的工厂函数
DynamicArray<int> arrayFactor(int size)
{
    DynamicArray<int> arr{ size };
    return arr;
}

int main()
{
    {
        DynamicArray<int> arr = arrayFactor(10);
    }

    getchar();
    return 0;
}

运行结果如下:

Constructor: dynamic array is created!
Move constructor: dynamic array is moved!
Destructor: dynamic array is destroyed!
Destructor: dynamic array is destroyed!

相关文章

网友评论

      本文标题:右值引用

      本文链接:https://www.haomeiwen.com/subject/zkcfnctx.html