万能引用和原样转发
1. 左值与右值
1.1 定义与分类
**左值(lvalue)和右值(rvalue)**是C++中用于描述表达式值类别的重要概念。
- 左值(lvalue):
- 表示具有持久存储的对象。
- 可以出现在赋值语句的左侧。
- 可以被取地址(即,可以使用
&运算符)。 - 示例:变量名、引用等。
- 右值(rvalue):
- 表示临时对象或没有持久存储的值。
- 通常出现在赋值语句的右侧。
- 不能被取地址。
- 示例:字面量、临时对象、表达式结果等。
C++11进一步细化了右值的分类:
- 纯右值(prvalues):表示临时对象或字面量,如
42、3.14。 - 将亡值(xvalues,expiring values):表示即将被移动的对象,如
std::move的结果。
1.2 示例代码
#include <iostream>#include <string>#include <utility>
int main() { int a = 10; // a是一个左值 int& b = a; // b是a的左值引用 int&& c = 20; // c是一个右值引用,绑定到临时右值20 int d = a + 5; // (a + 5)是一个纯右值
std::string s1 = "Hello"; // s1是一个左值 std::string s2 = std::string("World"); // std::string("World")是一个纯右值
std::cout << "a: " << a << ", d: " << d << std::endl; std::cout << "s1: " << s1 << ", s2: " << s2 << std::endl;
// 检查类型 std::cout << std::boolalpha; // 使用 decltype((a)) 获取 a 的类型,包括引用 // `(a)` 是一个左值表达式,所以 decltype((a)) 是 `int&` std::cout << "a 是左值: " << std::is_lvalue_reference<decltype((a))>::value << std::endl; std::cout << "c 是右值引用: " << std::is_rvalue_reference<decltype(c)>::value << std::endl;
return 0;}输出:
a: 10, d: 15s1: Hello, s2: Worlda 是左值: truec 是右值引用: true1.3 类型判断工具
C++标准库提供了几个类型特性(type traits)用于判断表达式的值类别:
std::is_lvalue_reference<T>:判断类型T是否为左值引用。std::is_rvalue_reference<T>:判断类型T是否为右值引用。std::is_lvalue<T>(C++20起):判断表达式是否为左值。std::is_rvalue<T>(C++20起):判断表达式是否为右值。
示例:
#include <iostream>#include <type_traits>
void func(int& x) { std::cout << "func(int&)" << std::endl;}
void func(int&& x) { std::cout << "func(int&&)" << std::endl;}
int main() { int a = 5; const int& ref = a;
// 判断类型 std::cout << std::boolalpha; // 使用 decltype((a)) 获取 a 的类型,包括引用 // `(a)` 是一个左值表达式,所以 decltype((a)) 是 `int&` std::cout << "a 是左值: " << std::is_lvalue_reference<decltype((a))>::value << std::endl; std::cout << "ref 是左值引用: " << std::is_lvalue_reference<decltype(ref)>::value << std::endl;
// 调用函数 func(a); // 调用func(int&) func(10); // 调用func(int&&)
return 0;}输出:
a 是左值: trueref 是左值引用: truefunc(int&)func(int&&)1.4 应用场景
理解左值与右值的区别是实现移动语义、完美转发以及高效内存管理的基础。在编写高性能C++代码时,合理利用右值引用和移动语义可以显著提升程序的效率。
2. 模板万能引用(转发引用)
2.1 定义与特性
模板万能引用(也称为转发引用,英文为forwarding references)是C++11引入的一种引用类型,具有以下特点:
- 表现形式为
T&&,其中T是模板参数。 - 在特定条件下,编译器会将其解析为左值引用或右值引用。
- 能够统一处理左值和右值,适用于泛型编程中的参数传递。
重要特性:
- 当模板参数
T被推导为普通类型时,T&&是右值引用。 - 当模板参数
T被推导为引用类型时,T&&会根据引用折叠规则解析为左值引用。
2.2 引用折叠规则
引用折叠是C++中的一项规则,用于处理引用类型的嵌套。主要规则如下:
& &、& &&、&& &都折叠为&(左值引用)。&& &&折叠为&&(右值引用)。
示例:
#include <type_traits>#include <iostream>
template<typename T>void check_referenc(T&& x){ std::cout << std::boolalpha; std::cout << "T is lvalue reference: " << std::is_lvalue_reference<T>::value << std::endl; std::cout << "T is rvalue reference: " << std::is_rvalue_reference<T>::value << std::endl; std::cout << "T is rvalue : " << std::is_rvalue_reference<T&&> ::value << std::endl; std::cout << "x is lvalue reference: " << std::is_lvalue_reference<decltype(x)>::value << std::endl; std::cout << "x is rvalue reference: " << std::is_rvalue_reference<decltype(x)>::value << std::endl;}
int main() { int a = 10; check_reference(a); // T被推导为 int&, 因此 T&& -> int& && -> int& check_reference(20); // T被推导为 int, T&& -> int&&
return 0;}输出:
T is lvalue reference: trueT is rvalue reference: falseT is rvalue : falsex is lvalue reference: truex is rvalue reference: falseT is lvalue reference: falseT is rvalue reference: falseT is rvalue : truex is lvalue reference: falsex is rvalue reference: true但是要注意,如果如果我这样调用
int &&c = 100;check_referenc(c);输出的却是
T is lvalue reference: trueT is rvalue reference: falseT is rvalue : falsex is lvalue reference: truex is rvalue reference: false然而,实际中 T 被推导为 int&,这是因为 命名的右值引用变量在表达式中被视为左值。
关键点:
- 变量的值类别(Value Category):
- 左值(lvalue):有名称,可以出现在赋值的左侧。
- 右值(rvalue):临时的、没有名称的值,一般不能出现在赋值的左侧。
- 命名的右值引用变量是左值:
- 虽然
c的类型是int&&,但作为一个变量名,它本身是一个左值。 - 这意味着在表达式中使用
c时,c被视为左值,而不是右值。
- 虽然
- 类型推导规则:
- 当模板参数使用
T&&(被称为转发引用或万能引用)时,类型推导遵循特定规则:- 如果传入的是左值,
T被推导为T&。 - 如果传入的是右值,
T被推导为T(非引用类型)。
- 如果传入的是左值,
- 当模板参数使用
- 传入左值(
c):c是一个命名的右值引用变量,但作为表达式它是左值。- 因此,
T被推导为int&。 T&&则根据引用折叠规则(int& &&=>int&)。
为了让 T 被推导为右值引用,我们需要传递一个 真正的右值。这可以通过使用 std::move 来实现:
// std::move(c) 是右值,T 被推导为 intcheck_reference(std::move(c));输出
T is lvalue reference: falseT is rvalue reference: falseT is rvalue : truex is lvalue reference: falsex is rvalue reference: true- **传递右值 **
std::move(c):std::move(c)将c转换为int&&(右值)。T被推导为int(非引用类型)。std::is_lvalue_reference<T>::value为false。std::is_rvalue_reference<T>::value为false。std::is_rvalue_reference<T&&>::value相当于std::is_rvalue_reference<int&&>::value,为true。decltype(x)相当于int&&,所以x被识别为右值引用。
- 为什么T被实例化为int而不是int&&
在模板参数中,当一个类型参数 T 以 T&& 的形式出现,并且 T 是模板参数时,T&& 被称为 转发引用(有时称为万能引用)。转发引用具有特殊的类型推导规则:
- 如果传递的是左值,
T被推导为T&。 - 如果传递的是右值,
T被推导为T(不带引用)。
模板类型推导的具体规则
根据 C++ 标准(C++17 标准草案):
如果 P 是
T&&,并且 A(实参类型)是一个非引用类型的表达式,则T被推导为 A 的类型。如果 P 是
T&&,并且 A 是一个引用类型的表达式,则去除引用后的类型用于推导。
具体到我们的例子:
- 传递
c(一个左值,类型int&&):- 表达式
c是一个 左值。 - 当传递左值给
T&&时,T被推导为int&(即int加上引用)。 - 因此,参数类型
T&&实际上是int& &&,根据引用折叠规则,int& &&合并为int&。
- 表达式
- 传递
std::move(c)(一个右值,类型int&&):- 表达式
std::move(c)是一个 右值。 - 当传递右值给
T&&时,T被推导为int(不带引用)。 - 因此,参数类型
T&&是int&&。
- 表达式
也可以采用原样转发,达到和move相同的效果
check_referenc(std::forward<int&&>(c));输出
T is lvalue reference: falseT is rvalue reference: falseT is rvalue : truex is lvalue reference: falsex is rvalue reference: true2.3 示例代码
以下示例展示了如何使用模板万能引用编写能够接受任意类型参数的函数,并通过转发保持参数的原有值类别。
#include <iostream>#include <string>#include <utility>
// 目标函数,分别有左值和右值的重载void process(int&x){ std::cout << "process(int&)" << std::endl;}
void process(int&& x){ std::cout << "process(int&&)" << std::endl;}
// 通用的包装函数,使用转发引用template<typename T>void wrapper(T&& arg) { process(std::forward<T>(arg)); // 原样转发}
int main() { int a = 10; wrapper(a); // 传递左值,调用process(int&) wrapper(20); // 传递右值,调用process(int&&) return 0;}输出:
process(int&)process(int&&)在上述代码中,wrapper函数使用模板万能引用T&&接收参数,通过std::forward<T>(arg)实现原样转发,确保传递给process函数的参数保持其原有的值类别。
如果我们把wrapper改为
// 通用的包装函数,使用转发引用template<typename T>void wrapper(T&& arg) { process(arg); // 原样转发}调用
int main() { int a = 10; wrapper(a); // 传递左值,调用process(int&) wrapper(20); // 传递右值,调用process(int&&) return 0;}则输出
process(int&)process(int&)因为虽然wrapper(20)调用时会将T实例化为int类型, arg变为int && 类型,但是arg本身是一个左值, 所以调用process会选择左值引用版本
// 目标函数void process(int&x){ std::cout << "process(int&)" << std::endl;}3. 类型推导(Type Deduction)
3.1 类型推导规则
在C++模板编程中,类型推导是指编译器根据传入的实参自动推导模板参数的类型。类型推导遵循以下基本规则:
- 模板参数的推导:
- 当模板参数与函数参数进行匹配时,编译器会根据实参类型推导出模板参数类型
T。
- 当模板参数与函数参数进行匹配时,编译器会根据实参类型推导出模板参数类型
- 引用的处理:
- 如果函数参数采用右值引用(如
T&&),且传递的是左值,则T会被推导为左值引用类型。 - 如果传递的是右值,则
T会被推导为非引用类型。
- 如果函数参数采用右值引用(如
- cv限定符的去除:
- 类型推导会去除顶层的
const和volatile修饰。
- 类型推导会去除顶层的
3.2 与引用的关系
在模板参数中使用引用类型时,类型推导会涉及引用折叠规则。例如,当一个模板函数参数为T&或T&&时,传入参数的值类别会影响T的推导结果。
示例:
#include <iostream>#include <type_traits>
template<typename T>void deduce_type(T&&) { std::cout << std::boolalpha; std::cout << "Is T an lvalue reference? " << std::is_lvalue_reference<T>::value << std::endl; std::cout << "Is T an rvalue reference? " << std::is_rvalue_reference<T>::value << std::endl;}
int main() { int a = 5; const int& ref = a;
deduce_type(a); // T被推导为 int& deduce_type(10); // T被推导为 int deduce_type(ref); // T被推导为 const int& deduce_type(std::move(a)); // T被推导为 int
return 0;}输出:
Is T an lvalue reference? trueIs T an rvalue reference? falseIs T an lvalue reference? falseIs T an rvalue reference? trueIs T an lvalue reference? trueIs T an rvalue reference? falseIs T an lvalue reference? falseIs T an rvalue reference? true3.3 示例代码
以下示例展示了不同情况下模板参数T的推导结果,以及如何利用类型推导编写通用函数。
#include <iostream>#include <type_traits>
template<typename T>void display_type(T&& param) { std::cout << "Type of T: " << (std::is_lvalue_reference<T>::value ? "lvalue reference" : "rvalue reference") << ", " << (std::is_integral<typename std::remove_reference<T>::type>::value ? "Integral" : "Non-Integral") << std::endl;}
int main() { int x = 10; const int& y = x; display_type(x); // T被推导为 int& display_type(20); // T被推导为 int&& display_type(y); // T被推导为 const int& display_type(std::move(x)); // T被推导为 int&&
return 0;}输出:
Type of T: lvalue reference, IntegralType of T: rvalue reference, IntegralType of T: lvalue reference, IntegralType of T: rvalue reference, Integral在此示例中,通过模板函数display_type,我们可以观察到不同参数传递方式下,模板参数T的推导结果。
4. 原样转发(Perfect Forwarding)
4.1 定义与作用
原样转发(Perfect Forwarding)是指在模板函数中,将接收到的参数以其原有的值类别(左值或右值)传递给另一个函数。这项技术确保了泛型代码能够像手写特定代码那样高效和正确地处理参数。
作用:
- 保持参数的值类别,确保正确调用函数重载。
- 利用移动语义,避免不必要的拷贝,提升性能。
- 编写通用、复用性强的代码。
4.2 实现原理
原样转发通常结合模板万能引用(转发引用)和std::forward来实现。具体流程如下:
- 使用模板万能引用(
T&&)接收参数。 - 使用
std::forward<T>(arg)将参数转发给目标函数。
- `std::forward`根据`T`的类型,将参数转换为左值或右值。- 如果参数原本是左值,`std::forward`返回左值引用。- 如果参数原本是右值,`std::forward`返回右值引用。4.3 为什么需要原样转发
在泛型编程中,函数模板可能需要将接收到的参数传递给其他函数。若不使用原样转发,参数会失去原有的值类别信息,可能导致以下问题:
- 右值参数被当作左值处理,无法利用移动语义,导致性能下降。
- 无法正确调用目标函数的重载版本。
- 增加不必要的拷贝开销,影响程序性能。
通过原样转发,可以确保参数在传递过程中保持其原有的左值或右值特性,提升代码的效率和灵活性。
4.4 示例代码
以下示例展示了如何实现一个通用的wrapper函数,通过原样转发将参数传递给目标函数,同时保留参数的值类别。
#include <iostream>#include <string>#include <utility>
// 目标函数,分别有左值和右值的重载void process(const std::string& s) { std::cout << "Processing lvalue: " << s << std::endl;}
void process(std::string&& s) { std::cout << "Processing rvalue: " << s << std::endl;}
// 通用的包装函数,实现原样转发template<typename T>void wrapper(T&& arg) { process(std::forward<T>(arg));}
int main() { std::string name = "Alice"; wrapper(name); // 传递左值,调用process(const std::string&) wrapper("Bob"); // 传递右值,调用process(std::string&&) wrapper(std::move(name)); // 传递右值,调用process(std::string&&)
return 0;}输出:
Processing lvalue: AliceProcessing rvalue: BobProcessing rvalue: Alice在上述代码中:
wrapper(name)传递的是左值,T被推导为std::string&,std::forward保持其为左值引用,调用process(const std::string&)。wrapper("Bob")传递的是右值,T被推导为const char*,在process函数重载的选择上,这里简单化处理为调用接收右值的版本(视具体实现而定)。wrapper(std::move(name))传递的是右值,T被推导为std::string,std::forward将其转换为右值引用,调用process(std::string&&)。
4.5 泛型工厂函数示例
以下示例展示了如何使用原样转发实现一个泛型工厂函数,完美转发构造函数的参数,以高效创建对象。
#include <iostream>#include <string>#include <utility>
// 类的定义class Person {public: std::string name; int age;
// 左值引用构造函数 Person(const std::string& n, int a) : name(n), age(a) { std::cout << "Constructed Person(const std::string&, int)" << std::endl; }
// 右值引用构造函数 Person(std::string&& n, int a) : name(std::move(n)), age(a) { std::cout << "Constructed Person(std::string&&, int)" << std::endl; }};
// 工厂函数,使用原样转发构造函数参数template<typename T, typename... Args>T create(Args&&... args) { return T(std::forward<Args>(args)...);}
int main() { std::string name = "Alice";
// 传递左值 Person p1 = create<Person>(name, 30); // 传递右值 Person p2 = create<Person>(std::string("Bob"), 25);
return 0;}输出:
Constructed Person(const std::string&, int)Constructed Person(std::string&&, int)在此示例中:
create<Person>(name, 30)传递的是左值,调用Person(const std::string&, int)构造函数。create<Person>(std::string("Bob"), 25)传递的是右值,调用Person(std::string&&, int)构造函数。
通过原样转发,create函数能够根据传入参数的值类别,调用相应的构造函数,实现高效的对象创建。
4.6 避免不必要的拷贝
以下示例展示了如果不使用原样转发,可能导致的多次拷贝问题。
#include <iostream>#include <string>
// 类的定义class BigObject {public: std::string data;
BigObject(const std::string& d) : data(d) { std::cout << "BigObject constructed with lvalue" << std::endl; }
BigObject(std::string&& d) : data(std::move(d)) { std::cout << "BigObject constructed with rvalue" << std::endl; }};
// 处理函数,左值和右值的重载void process(const BigObject& obj) { std::cout << "Processing lvalue BigObject" << std::endl;}
void process(BigObject&& obj) { std::cout << "Processing rvalue BigObject" << std::endl;}
// 包装函数,不使用原样转发template<typename T>void bad_wrapper(T&& obj) { process(obj); // 始终以左值形式传递}
// 包装函数,使用原样转发template<typename T>void good_wrapper(T&& obj) { process(std::forward<T>(obj)); // 原样转发}
int main() { std::string s = "Sample data";
// 使用bad_wrapper BigObject bo1 = BigObject(s); // 使用lvalue构造 BigObject bo2 = BigObject(std::move(s)); // 使用rvalue构造
bad_wrapper(bo1); // 始终作为左值处理 bad_wrapper(BigObject("Temp")); // 作为左值处理,浪费移动语义
// 使用good_wrapper good_wrapper(bo1); // 作为左值处理 good_wrapper(BigObject("Temp")); // 作为右值处理,利用移动语义
return 0;}输出:
BigObject constructed with lvalueBigObject constructed with rvalueProcessing lvalue BigObjectProcessing lvalue BigObjectBigObject constructed with rvalueProcessing rvalue BigObject在此示例中:
bad_wrapper函数不使用std::forward,导致即使传递的是右值,process函数也以左值形式接收,无法利用移动语义。good_wrapper函数使用std::forward,正确保持参数的值类别,允许process函数调用右值重载,从而利用移动语义,提高性能。
5. forward源码剖析
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">std::forward</font>之所以能进行原样转发,我们来看一下源码,<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">std::forward</font>实现了两个版本
返回左值引用版本
template<typename _Tp> _GLIBCXX_NODISCARD constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); }重点解释
1. **<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_GLIBCXX_NODISCARD</font>**
**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_GLIBCXX_NODISCARD</font>** 是一个宏,通常定义在 libstdc++(GNU的C++标准库实现)中,用于指示编译器不要忽略函数的返回值。具体来说,它通常被定义为 C++17 引入的 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">[[nodiscard]]</font> 属性,或者对应的编译器特定属性。
**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">[[nodiscard]]</font>**属性:这是C++17引入的一个属性,用于标记函数的返回值不应该被忽略。如果调用该函数的返回值被忽略,编译器会发出警告。例如:
[[nodiscard]] int compute() { return 42; }
void example() { compute(); // 编译器会发出警告,因为返回值被忽略}- 在
**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_GLIBCXX_NODISCARD</font>**中的作用:通过使用这个宏,<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">forward</font>函数的设计者告诉编译器和使用者:调用<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">forward</font>时,返回值不应该被忽略,因为它在转发参数时起到了关键作用。如果返回值被忽略,可能会导致意想不到的行为或性能问题。
总结:<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_GLIBCXX_NODISCARD</font> 确保使用 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">forward</font> 时,返回的转发结果不会被无意中忽略,增强代码的安全性和正确性。
2. **<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">constexpr</font>**
**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">constexpr</font>** 是C++11引入的一个关键字,用于指示一个函数或变量可以在编译时求值。
- 在函数中的作用:
- 编译时计算:
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">constexpr</font>函数可以在编译时被求值,如果传入的参数也都是编译时常量。 - 优化性能:通过在编译时进行计算,可以减少运行时的开销,提高程序的性能。
- 编译时计算:
- 在
**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">forward</font>**函数中的作用:- 高效实现:
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">forward</font>是一个轻量级的操作,通常涉及简单的静态类型转换(如<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">static_cast</font>)。将其定义为<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">constexpr</font>允许编译器在可能的情况下在编译时完成这些转换,从而避免不必要的运行时开销。 - 支持常量表达式:如果在编译时上下文中使用
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">forward</font>,例如在<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">constexpr</font>函数中,它能够在编译时正确地转发参数。
- 高效实现:
总结:<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">constexpr</font> 使得 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">forward</font> 函数能够在编译时进行优化,同时确保在运行时不引入额外的开销。
3. **<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">remove_reference</font>**作用
源码分析
template<typename _Tp> struct remove_reference { using type = _Tp; };
template<typename _Tp> struct remove_reference<_Tp&> { using type = _Tp; };
template<typename _Tp> struct remove_reference<_Tp&&> { using type = _Tp; };根据模板就近匹配规则,会按照最相似的方式进行匹配,优先匹配特例化版本,比如
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">remove_reference<int></font> 会调用第一个版本
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">remove_reference<int&></font>会调用第二个版本(特例化)
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">remove_reference<int&&></font>会调用第三个版本
所以我们对比这三个版本
remove_reference<int>::type;remove_reference<int&>::type;remove_reference<int&&>::type;这三个版本的type都为int,这样达到了去引用的效果。
所以带入
typename std::remove_reference<_Tp>::type& __t
我们可以指导__t一定是个左值引用类型。
原样转发内幕
此时我们考虑对一个左值引用类型(int& )参数执行forward函数
template<typename _Tp> _GLIBCXX_NODISCARD constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); }函数各部分解释:
- 模板参数
**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">typename _Tp</font>**: 定义了一个模板类型参数<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_Tp</font>,表示要转发的类型。 **<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">typename std::remove_reference<_Tp>::type& __t</font>**: 使用<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">std::remove_reference</font>去除<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_Tp</font>的引用性质,然后以左值引用的形式接受参数。**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">static_cast<_Tp&&>(__t)</font>**: 通过<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">static_cast</font>将参数转换为<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_Tp</font>类型的右值引用。这是实现完美转发的关键步骤,使得被转发的参数保留其原有的值类别。**<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">noexcept</font>**: 指明函数不会抛出异常,有助于编译器进行优化,并提高代码的异常安全性。
__t为int&类型,TP被实例化为int&, 通过引用折叠
int& std::forward<int&>(arg)
达到了原样转发的目的。
模板参数 **<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">typename _Tp</font>**: 定义了一个模板类型参数 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_Tp</font>,表示要转发的类型。
同样我们分析右值引用类型原样转发原理
返回右值引用版本
template<typename _Tp> _GLIBCXX_NODISCARD constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "std::forward must not be used to convert an rvalue to an lvalue"); return static_cast<_Tp&&>(__t); }这里限制了不能将右值引用转化为左值引用。
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">__t</font>是右值引用类型,当我们执行如下
int&& forward<int&&>(args)
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">_Tp</font>被实例化为<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">int&&</font>, 返回类型通过折叠变为<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">int&&</font> , 参数<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">__t</font>变为<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">int&&</font>类型
6. move源码解析
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">std::move</font>源码
template<typename _Tp> _GLIBCXX_NODISCARD constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }可以看到move的实现很简单,无论<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">__t</font>是左值引用还是右值引用,都会通过
static_cast<typename std::remove_reference<_Tp>::type&&>(__t)
强制转换为去除引用的type, 再加上<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">&&</font>的类型,也就是具体的右值引用类型。
7. 总结
- 左值与右值:理解值类别是掌握C++移动语义、资源管理和高效编程的基础。左值代表具有持久存储的对象,而右值通常是临时对象。
- 模板万能引用(转发引用):通过
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">T&&</font>形式的模板参数,可以统一处理左值和右值,适用于泛型编程中的参数传递。引用折叠规则决定了<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">T&&</font>在不同上下文中的解析方式。 - 类型推导:编译器根据实参自动推导模板参数类型,涉及引用类型时需要理解引用折叠规则和类型推导的细节。
- 原样转发(Perfect Forwarding):结合模板万能引用和
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">std::forward</font>,确保在泛型函数中将参数以其原有值类别传递给目标函数,避免不必要的拷贝,提升代码效率和灵活性。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!







