1. 完美转发的概念
完美转发(Perfect Forwarding)是 C++11 中引入的一种编程技巧,其目的是在编写泛型函数时能够保留参数的类型和值类别(左值或右值),从而实现更为高效且准确地传递参数。通过使用右值引用和模板类型推导,完美转发允许我们在函数中以原始参数的形式将参数传递给其他函数,而不会发生不必要的拷贝操作,从而提高性能。
2. 左值与左值引用
左值是表达式的一种属性,表示可以出现在赋值运算符左侧的值。左值引用是 C++ 中传统的引用类型,用符号 ‘&’ 表示。左值引用可以绑定到左值,从而实现对左值的引用和修改。例如:
int x = 10;
int& ref_x = x; // 左值引用绑定到左值 x
ref_x = 20; // 通过引用修改 x 的值
3. 右值与右值引用
右值是指不能出现在赋值运算符左侧的表达式,通常表示临时对象或即将被销毁的对象。C++11 引入了右值引用,用符号 ‘&&’ 表示。右值引用可以绑定到右值,从而实现对右值的引用和修改。例如:
int&& ref_rv = 42; // 右值引用绑定到一个临时整数对象
ref_rv = 55; // 通过引用修改右值
4. std::move 的作用
std::move 是一个将左值转换为右值引用的工具,它可以让我们在需要时将左值当作右值使用。例如,我们可以使用 std::move 实现对象的移动语义,从而避免不必要的拷贝操作。例如:
std::vector v1 = {1, 2, 3};
std::vector v2 = std::move(v1); // 使用 std::move 将 v1 的资源移动到 v2,避免了拷贝操作
5. 万能引用
C++ 11中有万能引用(Universal Reference)的概念:使用T&&类型的形参既能绑定右值,又能绑定左值。
但是注意了:只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。
6. forward的作用
std::forward被称为完美转发,它的作用是保持原来的值属性不变。啥意思呢?通俗的讲就是,如果原来的值是左值,经std::forward处理后该值还是左值;如果原来的值是右值,经std::forward处理后它还是右值。
#include<iostream>
template<typename T>
void print(T & t)
{
std::cout<<"左值"<<std::endl;
}
template<typename T>
void print(T && t)
{
std::cout<<"右值"<<std::endl;
}
template<typename T>
void testForward(T && v)
{
print(v);
print(std::forward<T>(v));
print(std::move(v));
}
int main()
{
testForward(1);
std::cout << "======================" << std::endl;
int x = 1;
testForward(x);
return 0;
}
左值
右值
右值
=========================
左值
左值
右值
从上面第一组的结果我们可以看到,传入的1虽然是右值,但经过函数传参之后它变成了左值(在内存中分配了空间);而第二行由于使用了std::forward函数,所以不会改变它的右值属性,因此会调用参数为右值引用的print模板函数;第三行,因为std::move会将传入的参数强制转成右值,所以结果一定是右值。
再来看看第二组结果。因为x变量是左值,所以第一行一定是左值;第二行使用forward处理,它依然会让其保持左值,所以第二也是左值;最后一行使用move函数,因此一定是右值。
7. std::forward内部实现
/**
* @brief Forward an lvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
/**
* @brief Forward an rvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
从内部实现看std::forward(v)实际上就是做了一个static_cast的强转, 这个转换过程不是发生在函数内部,而是在函数外部就已经判断出来了_Tp是左值还是右值
std::forward必须配合T&&来使用。例如T&&接受左值int&时,T会被推断为int&,而T&&接受右值int&&时,T被推断为int。在std::forward中只是单纯的返回T&&。那么依据外层是左值时,T为int&,那么T&&即int& &&仍为int&,当外层函数参数为右值,T&&为int&&,这样就保证了传进来是左值则还是左值,是右值还是右值。
8. 常见的完美转发应用场景
用完美转发实现委托构造函数
委托构造函数允许一个构造函数调用同一个类的其他构造函数,从而避免代码重复。通过使用完美转发,我们可以更高效地在构造函数间传递参数。
class MyString {
public:
template <typename... Args>
MyString(Args&&... args) : _data(std::forward<Args>(args)...)
{
}
private:
std::string _data;
};
int main()
{
MyString s1("Hello, world!");
// 调用 std::string 的构造函数MyString s2(s1);
// 调用 std::string 的拷贝构造函数MyString s3(std::move(s2));
// 调用 std::string 的移动构造函数
}
用完美转发实现可变参数模板函数
可变参数模板函数可以接受任意数量和类型的参数,通过使用完美转发,我们可以实现一个通用的元组或 bind 函数。
template <typename Func, typename... Args>
auto bind_and_call(Func&& func, Args&&... args) -> decltype(func(std::forward<Args>(args)...))
{
return func(std::forward<Args>(args)...);
}
int sum(int a, int b, int c)
{
return a + b + c;
}
int main()
{
int result = bind_and_call(sum, 1, 2, 3); // 完美转发参数给 sum 函数
}
用完美转发实现智能指针
智能指针是一种自动管理内存生命周期的对象,它可以确保在离开作用域时自动释放内存。通过使用完美转发,我们可以在智能指针的构造函数和 make 函数中避免不必要的拷贝操作
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
class MyClass {
public:
MyClass(int x, double y) : _x(x), _y(y)
{
}
private:
int _x;
double _y;
};
int main()
{
auto ptr = make_unique<MyClass>(42, 3.14); // 完美转发参数给 MyClass 的构造函数
}