move与forward详解

2019-05-20  本文已影响0人  advanced_slowly

move与forward内部实现分析

为了减少不必要的昂贵的拷贝代价,c++11提出了move semantic(移动语义),直接将右值(例如一些临时对象)的资源“偷”过来,从而避免了不必要的拷贝,提高了程序的执行效率。

在utility头文件下定义了move和forward函数模板,在分析之前先看三个结构体模板remove_reference

// STRUCT TEMPLATE remove_reference
//泛型模板
template <class _Ty>
struct remove_reference { // remove reference
    using type = _Ty;
};

//模板的特化,不是偏特化
template <class _Ty>
struct remove_reference<_Ty&> { // remove reference
    using type = _Ty;
};

//模板的特化,不是偏特化
template <class _Ty>
struct remove_reference<_Ty&&> { // remove rvalue reference
    using type = _Ty;
};

从上诉三个类模板可以得到的信息为type是待推导数据类型_Ty的别名,并且_Ty的数据类型是去除引用(包括左值引用和右值引用)后的。

一个宏定义remove_reference_t:

template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;

从上诉宏定义可见,remove_reference也是待推导类型_Ty的别名

move函数模板源代码:

// FUNCTION TEMPLATE move
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

依据前一节模板类型推导的知识,当调用move函数时,函数模板类型推导需要推导_Ty和形式参数_Arg的数据类型。因move函数模板的参数类型为universal reference,所以_Arg的参数类型只能被推倒为以下几种:const左值引用(保留实参的const特性下),左值引用,右值引用。但不管_Arg被推倒为哪一种,它都将会通过类型转换转换为右值引用类型。因为有这三个结构体模板remove_reference发挥着作用。当调用move函数模板的实参是右值引用时,_Ty被推导为int,此时第一个remove_reference发挥着作用。当调用move函数模板的实参是左值引用时,_Ty被推导为int &,此时第二个remove_reference发挥着作用。

举个简单的例子(这里以内置类型只做简单例子,实际意义不大):

# include<iostream>

void printI(int&& i)
{
    std::cout << i << std::endl;
}
int main()
{
    int a = 100;

    printI(std::move(a));   //a为左值,打印i的值为100
    printI(std::move(100)); //100为右值,打印i的值为100

    return 0;
}

# include <iostream>
# include <string>

class Example0
{
private:
    int x;
public:
    Example0(int x = 0):x(x){}

    Example0(const Example0 & ex):x(ex.x)
    {
        std::cout << "The copy constructor are called" << std::endl;
    }

    Example0(Example0 && ex):x(ex.x)
    {
        ex.x = 0;
        std::cout << "The move constructor are called" << std::endl;
    }
};

class Example1
{
private:
    Example0 ex;
public:
    Example1(const Example0 e) :ex(std::move(e))
    {

    }

    /*
    Example1( Example0 e) :ex(std::move(e))
    {

    }
    */

};

int main()
{
    Example0 ex0(0);

    Example1 ex1(ex0);

    return 0;
}

上诉例子中Example1构造函数的参数有无const将是两个不一样的结果:有const,调用两次copy constructor。无const,调用一次copy constructor和一次move constructor。为什么有const会是两次调用copy constructor?

在下面这段代码中

Example1(const Example0 e) :ex(std::move(e))
 {

 }

e确实被move无条件的转化为了右值,但是编译器在根据无条件转化后的右值实参匹配调用构造函数时,由于move constructor的形参是一个不带const的右值引用,所以还是会调用copy constructor。

forward函数模板源码:

// 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);
}

第一个forward function template中,模板参数是一个左值。当调用该函数的实参为左值时,这个template就将forward an lvalue as lvalue.当调用该函数的实参为右值时,这个template就将forward an lvalue as rvalue.

第二个forward function template中,模板参数是一个右值。当调用该函数的实参为左值时,这个template就将执行static_assert这一句,不进行转换。当调用该函数的实参为右值时,这个template就将forward an rvalue as rvalue.

forward的作用将一个对象转发给另一个对象,同时保留该对象的左值性或右值性。

注:以上源码来自vs2019,不同编译器,实作可能不同,例如上诉源码所在头文件可能不同,move的内部实现可能不同。

上一篇下一篇

猜你喜欢

热点阅读