C++ 新特性 | C++ 11 | std::forward、万能引用与完美转发


概述

std::forward是C++11中引入的一个函数模板,用于实现完美转发。它的作用是根据传入的参数,决定将参数以左值引用还是右值引用的方式进行转发。

传统上,当一个左值传递给一个函数时,参数会以左值引用的方式进行传递;当一个右值传递给一个函数时,参数会以右值引用的方式进行传递。完美转发是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题。std::forward实现完美转发主要用于以下场景:提高模板函数参数传递过程的转发效率

一、std::forward、万能引用与完美转发

1、万能引用

对于形如T&&的变量或者参数,如果T可以进行推导,那么T&&称之为万能引用。换句话说,对于形如T&&的类型来说,其既可以绑定左值,又可以绑定右值,而这个的前提是T需要进行推导。最常见的万能引用方式如以下两种:

函数模板:

template<typename T>
void f(T&& param); // 存在类型推导,param是一个万能引用

auto类型推导:

auto&& var = var1; // 存在类型推导,var是一个万能引用

注意:只有当发生自动类型推断时(例如:函数模板的类型自动推导),T &&才是万能引用。下面一个示例中的&& 并不是一个万能引用,例如:

template<typename T>
void f( T&& param); // 这里T的类型需要推导,所以&&是一个 universal references

template<typename T>
class Test {
	Test(Test&& rhs);  // Test是一个特定的类型,不需要类型推导,所以&&表示右值引用  
};

2、类型推导

万能引用进行类型推导时需要推导出T&&中的T的真实类型:若传入的参数是一个左值,则T会被推导为左值引用;而如果传入的参数是一个右值,则T会被推导为原生类型(非引用类型)。

对于万能引用来说,条件之一就是类型推导,但是类型推导是万能引用的必要非充分条件,也就是说参数必须被声明为T&&形式不一定是万能引用。示例如下:

template<typename T>
void func(std::vector<T>&& t); // t是右值引用

调用func时会执行类型推导,但是参数t的类型声明的形式并非T &&而是std::vector &&。 之前强调过,万能引用必须是T &&才行,因此,t是一个右值引用,如果尝试将左值传入,编译器将会报错:

std::vector<int> v;
fun(v); // 编译错误,不能将左值绑定到右值

形如const T&&的方式也不是万能引用:

template<typename T>
void f(const T&& t); // t是右值引用

int main() {
  int a = 0;
  f(a); // 错误
}

3、引用折叠

引用折叠是一种特性,允许在模板元编程中使用引用类型的参数来创建新的引用类型。由于存在T&&这种万能引用类型,当它作为参数时,有可能被一个左值/左值引用或右值/右值引用的参数初始化,这需要通过类型推导,推导后得到的参数类型会发生类型变化,这种变化就称为引用折叠。

根本原因是因为C++中禁止reference to reference,所以编译器需要对四种情况(& && &&&& &,&& &&)进行处理,将他们折叠成一种单一的reference。引用折叠的规则如下:如果两个引用中至少其中一个引用是左值引用,那么折叠结果就是左值引用;否则折叠结果就是右值引用。示例如下:

using T = int &;
T& r1;  // int& & r1 -> int& r1
T&& r2; // int& && r2 -> int& r2
  
using U = int &&;
U& r3;  // int&& & r3 -> int& r3
U&& r4; // int&& && r4 -> int&& r4

下面是一个具体的示例,可以看下对应的推导过程

template<typename T>
void func(T &&t) {
    cout << "hello world" << endl;
}

int main() {
    int a = 1;
    int &b = a;
    func(a); // T 推导成 int &; T && ==> int & && ==> int &
    func(b); // T 推导成 int &; T && ==> int & && ==> int &
    func(1); // T 推导成 int; T && ==> int &&
    func(std::move(a)); // T 推导成 int &&; T && ==> int && && ==> int &&
    return 0;
}

4、std::forward

完美转发是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题,std::forward源码如下:

template<class T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
  return static_cast<T&&>(t);
}

template <class T>
T&& forward(typename std::remove_reference<T>::type&& t) noexcept {
  return static_cast<T&&>(t);
}

其内部实现只有一行代码,即static_cast<T&&>(t)使用static_cast<>进行类型转换,与std::move()实现方式类似。结合前面介绍的引用折叠,当接收一个左值作为参数时,std::forward<>()返回左值引用,相应的,当接收一个右值作为参数时,std::forward<>()返回右值引用。

版本一:没有实现完美转发

下面给出一个案例没有实现完美转发,如下:

#include <iostream>

template <typename T>
void wrapper(T u) {
    fun(u);
}

class MyClass {};

void fun(MyClass& a) { std::cout << "in fun(MyClass&)\n"; }
void fun(const MyClass& a) { std::cout << "in fun(const MyClass&)\n"; }
void fun(MyClass&& a) { std::cout << "in fun(MyClass &&)\n"; }

int main(void) {
    MyClass a;
    const MyClass b;

    fun(a);
    fun(b);
    fun(MyClass());

    std::cout << "----- Wrapper ------\n";
    wrapper(a);
    wrapper(b);
    wrapper(MyClass());

    return 0;
}

输出结果:

in func(MyClass&)
in func(const MyClass&)
in func(MyClass &&)
----- Wrapper ------
in func(MyClass&)
in func(const MyClass&)
in func(const MyClass&)

Process returned 0 (0x0)   execution time : 0.253 s
Press any key to continue.

最后一行函数调用结果不符合预期,传入的是MyClass &&右值引用,预期调用fun(MyClass&& a),实际上调用的却是fun(const MyClass& a)。调用wrapper函数时触发拷贝构造,基于右值创建了左值u(即:wrapper函数的参数),u的实际类型是const MyClass,匹配的是fun(const MyClass& a)

版本二:使用std::forward实现完美转发

使用万能引用和完美转发来修改前面的例子,如下:

#include <iostream>

template <typename T>
void wrapper(T &&u) { // 万能引用
    func(std::forward<T>(u)); // 完美转发
}

class MyClass {};

void func(MyClass& a) { std::cout << "in func(MyClass&)\n"; }
void func(const MyClass& a) { std::cout << "in func(const MyClass&)\n"; }
void func(MyClass&& a) { std::cout << "in func(MyClass &&)\n"; }

int main(void) {
    MyClass a;
    const MyClass b;

    func(a);
    func(b);
    func(MyClass());

    std::cout << "----- Wrapper ------\n";
    wrapper(a);
    wrapper(b);
    wrapper(MyClass());

    return 0;
}

输出结果:

in func(MyClass&)
in func(const MyClass&)
in func(MyClass &&)
----- Wrapper ------
in func(MyClass&)
in func(const MyClass&)
in func(MyClass &&)

Process returned 0 (0x0)   execution time : 0.210 s
Press any key to continue.

输出结果符合预期,使用std::forward实现了完美转发。

注意std::forward()建议仅用于模板函数,对于非模板的,因为不涉及到类型推导,所以使用完美转发是没有意义的。

https://mp.weixin.qq.com/s/bT–OIA7X8Uohxl_q1O98A

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
std::move是C++11中的一个标准库函数,用于将一个左值强制转换为右值引用。通过使用std::move,可以告诉编译器一个对象可以被移动而不是复制,从而提高程序的性能。std::move是一个类型转换函数,它不会真正移动数据,只是将左值转换成右值引用std::forward也是C++11中的一个标准库函数,用于完美转发参数。当我们希望将一个函数的参数传递给另一个函数时,我们可以使用std::forward来保持参数的左右值属性。std::forward根据传入的参数类型来决定是将参数作为左值引用还是右值引用进行传递。 左右值引用C++11中引入的一个新的引用类型。左值引用指向一个具名的对象,而右值引用则可以绑定到一个临时对象或将要销毁的对象。左右值引用的一个重要应用是移动语义,通过将资源所有权从一个对象转移到另一个对象,避免了昂贵的资源拷贝操作。 移动构造函数是一种特殊的构造函数,用于在对象的移动操作中进行资源移动而不是拷贝。在C++11中,当一个对象被移动时,编译器会首先尝试调用其移动构造函数。移动构造函数需要一个右值引用作为参数,并将其它对象的资源移动到当前对象中,然后将原来的对象置为有效的但未知的状态。 综上所述,C++11中的std::move和std::forward以及左右值引用与移动构造函数都是为了实现移动语义而引入的新特性。它们可以提高程序的性能,避免不必要的资源拷贝,以及实现更高效的对象移动操作。但是在使用时需要注意正确的使用方式和避免潜在的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值