LOADING

加载过慢请开启缓存 浏览器默认开启

C++ 笔记(1)—— 万能引用/完美转发/引用折叠

2024/3/27 Language C++

std::move

std::move 的源码长这样:

template <class _Tp>
_LIBCPP_NODISCARD_EXT inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR typename remove_reference<_Tp>::type&&
move(_Tp&& __t) _NOEXCEPT {
  typedef _LIBCPP_NODEBUG typename remove_reference<_Tp>::type _Up;
  return static_cast<_Up&&>(__t);
}

它本质上是将一个引用强制转换成了右值引用

万能引用(Universal Reference)

当我们使用 T && x 或者 auto && x 这种需要类型推断的右值形式的时候,就会被编译器作为万能引用。所谓万能引用,就是既能绑定左值引用,又能绑定右值引用。(所以说,在 C++ 中,并不是说两个 & 就代表是右值引用)。

引用折叠(Reference Collapse)

  • 左值-左值 T& &:函数定义的形参类型是左值引用,传入的实参是左值引用
  • 左值-右值 T& &&:函数定义的形参类型是左值引用,传入的实参是右值引用
  • 右值-左值 T&& &:函数定义的形参类型是右值引用,传入的实参是左值引用
  • 右值-右值 T&& &&:函数定义的形参类型是右值引用,传入的实参是右值引用
    但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:

所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。

#include <iostream>
template <typename T>
void PrintInner(T &x)
{
    std::cout << "I got a lvalue: " << x << std::endl;
}

template <typename T>
void PrintInner(T &&x)
{
    std::cout << "I got a rvalue: " << x << std::endl;
}

template <typename T>
void Print(T &&x)
{
    PrintInner(x);
    PrintInner(std::move(x));
    PrintInner(std::forward<T>(x));
}

int main()
{
    Print(5);
    std::cout << "-------------------" << std::endl;

    int x = 20;
    Print(x);
    std::cout << "-------------------" << std::endl;

    auto &&x2 = 30;
    std::cout << std::boolalpha << (typeid(x2) == typeid(int &&)) << std::endl;
    std::cout << "-------------------" << std::endl;

    auto &&x3 = x;
    std::cout << std::boolalpha << (typeid(x3) == typeid(int &)) << std::endl;
}

上面这个例子最终会输出:

I got a lvalue: 5
I got a rvalue: 5
I got a rvalue: 5
-------------------
I got a lvalue: 20
I got a rvalue: 20
I got a lvalue: 20
-------------------
true
-------------------
true

观察:

  • 虽然 5 是作为右值被传入函数 Print,但是这个右值被绑定到了形参 x 上,所以 PrintInner 最终调用的是左值引用的版本。
  • std::move 毋庸置疑,本质上就是都强制转换成右值引用。
  • std::forward 为了将 5 继续当作右值转发,我们可以使用 std::forward,也就是 完美转发
  • 事实上,可以将 T && x 和 T &x 合并成 T &&,因为我们有万能引用。

常左值引用可以绑定一个右值。

const std::string && func()  {
    return std::move(std::string("ok"));
}
const std::string &ok = func(); // ok
const std::string &ok = "ok";   // ok

std::string &fail = "fail";
error: non-const lvalue reference to type 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char>>') cannot bind to a value of unrelated type 'const char[5]'
    std::string &fail = "fail";