转移和完美转发

文章参考:

爱编程的大丙 (subingwen.cn)

1. move

作用:主要作用有两点:

  • 实现用左值来初始化右值引用。
  • 进行对象间的资源转移。(避免使用拷贝,从而提高效率)

特点:

  • 可以将左值转化为右值。
  • 该函数和移动构造函数一样,都有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,且进行内存拷贝。

原型:从实现上看,std::move()基类等同于一个类型转移static_cast<T&&>(lvalue)

template <class _Ty>
_NODISCARD constexr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT{
    return static_cast<remove_reference_t<_Ty>&&> (_Arg));
}

Eg:

  • 用左值来初始化右值引用

    #include <iostream>
    class Test {
    public: 
        Test(){}
    };
    
    int main(void){
        Test t;				
        Test& lt = t;
        Test&& rt = std::move(t);		
        return 0;
    }
    
    • t是左值,但通过std::move(),可以用左值对右值引用进行初始化。
  • 实现对象间的资源转移,无需复制:

    #include <iostream>
    #include <list>
    using namespace std;
    
    int main(void){
        list<int> l1 = {1, 2, 3, 4, 5};
        list<int> l2 = move(l1);
        cout << "l1:";
        for(auto t: l1){
            cout << t << " ";
        }
        cout<<endl;
        cout << "l2:";
        for(auto t : l2){
            cout << t << " ";
        }
        cout << endl;
        return 0;
    }
    
    • 输出:

      l1:
      l2:1 2 3 4 5 
      
    • 分析:因为使用了std::move(),列表l1的资源被转移给了列表l2,因此列表l1没有资源了。

2. forward

引入:

右值引用是独立于值的,当一个右值引用作为函数的形参时,如果在该函数内部调用该形参,将其转发给其余函数时,该形参就变成了一个左值。如果我们想要它依旧是一个右值,那么就要使用C++11提供的std::forward()函数,也就是所谓的完美转发

函数原型:

template <class T>
T&& forward (typename remove_reference<T>::type& t) noexcept;

template <class T>
T&& forward (typename remove_reference<T>::type&& t) noexcept;
  • 精简后:

    std::forward<T>(t)
    

效果:

  • 如果传入的T是左值引用类型,那么t将会被转换为T类型的左值。
  • 当T不是左值引用类型时,t将会被转换为T类型的右值。

EG:

  • 代码:

    #include <iostream>
    using namespace std;
    
    template<typename T>
    void printValue(T& t)
    {
        cout << "l-value: " << t << endl;
    }
    
    template<typename T>
    void printValue(T&& t)
    {
        cout << "r-value: " << t << endl;
    }
    
    template<typename T>
    void testForward(T && v)
    {
        printValue(v);
        printValue(move(v));
        printValue(forward<T>(v));
        cout << endl;
    }
    
    int main()
    {
        testForward(520);
        int num = 1314;
        testForward(num);
        testForward(forward<int>(num));
        testForward(forward<int&>(num));
        testForward(forward<int&&>(num));
    
        return 0;
    }
    
  • 输出:

    l-value: 520
    r-value: 520
    r-value: 520
    
    l-value: 1314
    r-value: 1314
    l-value: 1314
    
    l-value: 1314
    r-value: 1314
    r-value: 1314
      
    l-value: 1314
    r-value: 1314
    l-value: 1314
        
    l-value: 1314
    r-value: 1314
    r-value: 1314
    
  • 分析:

    • testForward(520):函数形参T&&为未定义引用类型,实参为右值,初始化后被推导为一个右值引用。
      • printValue(v):已命名的右值v,编译器会视作左值处理,因此实参是左值
      • printValue(move(v)):已命名的右值被编译器视作左值处理,随后又通过move转换为右值,因此实参是右值
      • printValue(forward<T>(v)):已命名的右值被编译器视作左值处理,而此处的模板参数T为右值引用,因此参数被完美转发成一个右值,因此实参是右值
    • testForward(num):函数形参T&&为未定义引用类型,实参为左值,初始化后被推导为一个左值引用。
      • printValue(v):实参是左值
      • printValue(move(v)):左值通过move转换为右值,因此实参是右值
      • printValue(forward<T>(v)):forward的模板参数为左值引用,最终得到一个左值引用,实参为左值
    • testForward(forward<int>(num)):forward的模板参数不是左值引用,因此num被完美转发为右值,函数形参T&&为未定义引用类型,最终实参为右值
      • printValue(v):已命名的右值v,编译器会视作左值处理,因此实参是左值
      • printValue(move(v)):已命名的右值被编译器视作左值处理,随后又通过move转换为右值,因此实参是右值
      • printValue(forward<T>(v)):已命名的右值被编译器视作左值处理,而此处的模板参数T为右值引用,因此参数被完美转发成一个右值,因此实参是右值
    • testForward(forward<int&>(num)):forward的模板参数int&是左值引用,因此num被完美转发为左值,函数形参T&&为未定义引用类型,最终实参为左值
      • printValue(v):实参是左值
      • printValue(move(v)):左值通过move转换为右值,因此实参是右值
      • printValue(forward<T>(v)):forward的模板参数为左值引用,最终得到一个左值引用,实参为左值
    • testForward(forward<int&&>(num)):forward的模板参数int&&不是左值引用,因此num被完美转发为右值,函数形参T&&为未定义引用类型,最终实参为
      • printValue(v):已命名的右值v,编译器会视作左值处理,因此实参是左值
      • printValue(move(v)):已命名的右值被编译器视作左值处理,随后又通过move转换为右值,因此实参是右值
      • printValue(forward<T>(v)):已命名的右值被编译器视作左值处理,而此处的模板参数T为右值引用,因此参数被完美转发成一个右值,因此实参是右值
posted @   BinaryPrinter  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
点击右上角即可分享
微信分享提示