浅谈右值引用 移动语义 完美转发 std::move std::forward,窥探模板元编程的一角
右值引用 移动语义 完美转发具体是什么,就不说了,网上一搜一大堆,主要介绍下std::move和std::forward
std::move std::forward
查下源码,gcc版本:gcc version 7.3.0 (GCC),grep -r "forward(" /usr/include/c++/7.3.0/bits/,move和forward都在/usr/include/c++/7.3.0/bits/move.h文件中,源码如下:

/**
92 * @brief Convert a value to an rvalue.
93 * @param __t A thing of arbitrary type.
94 * @return The parameter cast to an rvalue-reference to allow moving it.
95*/ 96 template<typename _Tp> 97 constexpr typename std::remove_reference<_Tp>::type&& 98 move(_Tp&&__t) noexcept99 { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }/**
66 * @brief Forward an lvalue.
67 * @return The parameter cast to the specified type.
68 *
69 * This function is used to implement "perfect forwarding".
70*/ 71 template<typename _Tp> 72 constexpr _Tp&& 73 forward(typename std::remove_reference<_Tp>::type&__t) noexcept74 { return static_cast<_Tp&&>(__t); }75 76 /**
77 * @brief Forward an rvalue.
78 * @return The parameter cast to the specified type.
79 *
80 * This function is used to implement "perfect forwarding".
81*/ 82 template<typename _Tp> 83 constexpr _Tp&& 84 forward(typename std::remove_reference<_Tp>::type&&__t) noexcept85{86 static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" 87 "substituting _Tp is an lvalue reference type");88 return static_cast<_Tp&&>(__t);89 }
move forward
本质就是强制类型转换,move并不进行所谓的“移动”
用c++14实现一下,更简单,如下:

//C++14 version of std::move template<typename _Tp>constexpr decltype(auto)
move(_Tp&&__t) noexcept
{return static_cast<std::remove_reference_t<_Tp>&&>(__t);
}//C++14 version of std::forward for lvalues template<typename _Tp>constexpr decltype(auto)
forward(std::remove_reference_t<_Tp>&__t) noexcept
{return static_cast<_Tp&&>(__t);
}//C++14 version of std::forward for rvalues template<typename _Tp>constexpr decltype(auto)
forward(std::remove_reference_t<_Tp>&&__t) noexcept
{
static_assert(!std::is_lvalue_reference_v<_Tp>, "template argument substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);
}
c++14 move forward
写了一个测试程序,如下:

#include <iostream>#include<utility> //for std::move, std::forward #include <type_traits> //for remove_reference_t, is_lvalue_reference_v//C++14 version of std::move template<typename _Tp>constexpr decltype(auto)
move(_Tp&&__t) noexcept
{return static_cast<std::remove_reference_t<_Tp>&&>(__t);
}//C++14 version of std::forward for lvalues template<typename _Tp>constexpr decltype(auto)
forward(std::remove_reference_t<_Tp>&__t) noexcept
{return static_cast<_Tp&&>(__t);
}//C++14 version of std::forward for rvalues template<typename _Tp>constexpr decltype(auto)
forward(std::remove_reference_t<_Tp>&&__t) noexcept
{
static_assert(!std::is_lvalue_reference_v<_Tp>, "template argument substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);
}//Test class with move and copy constructors classWidget {public:
Widget() { std::cout<< "Widget default constructor\n"; }
Widget(const Widget&) {
std::cout<< "Widget copy constructor\n";
}
Widget(Widget&&) noexcept {
std::cout<< "Widget move constructor\n";
}
};//Function to test std::forward template <typename T> void forward_test(T&&arg) {
Widget w= std::forward<T>(arg);
}intmain() {//Test std::move Widget widget1;
std::cout<< "Using std::move:\n";
Widget widget2= std::move(widget1); //Should call move constructor//Test std::forward with lvalue std::cout << "\nUsing std::forward with lvalue:\n";
Widget widget3;
forward_test(widget3);//Should call copy constructor//Test std::forward with rvalue std::cout << "\nUsing std::forward with rvalue:\n";
forward_test(Widget());//Should call move constructor return 0;
}
test
因为is_lvalue_reference_v c++17才支持,所以编译:g++ test_move_forward.cpp -o test_move_forward -std=c++17
标签分发
有个全局的names,需要定义两个函数,一个是函数模板用的万能引用,一个函数的参数是普通的int(通过id检索到name,省略此实现),代码如下:

#include <iostream>#include<type_traits>#include<utility> //for std::forward #include <unordered_set> //全局数据结构 std::unordered_set<std::string>names;//日志函数 void log(const char*message) {
std::cout<< "Log:" << message <<std::endl;
}//模板版本 template<typename T> void logAndAdd(T&&name) {
log("logAndAdd (perfect forwarding)");
names.emplace(std::forward<T>(name));
}void logAndAdd(intidx) {
log("logAndAdd (int version)");//处理 int 类型的逻辑 }intmain() {
std::string name = "Alice";int idx = 42;//测试左值 logAndAdd(name); //应该调用模板版本//测试右值 logAndAdd(std::string("Bob")); //应该调用模板版本//测试 int 类型 logAndAdd(idx);//测试 short 类型 short idx2 = 222;
logAndAdd(idx2);return 0;
}
标签分发
上面的代码,没有测试 short 类型的那两行代码,是没问题的,但测试 short 类型的会匹配到完美转发那个函数,下面先用标签分发解决一下,代码如下:

#include <iostream>#include<type_traits>#include<unordered_set>#include<chrono>#include<utility> //for std::forward, std::move> #include <string> //全局数据结构 std::unordered_set<std::string>names;//日志函数 void log(const char*message) {
auto now=std::chrono::system_clock::now();
auto time=std::chrono::system_clock::to_time_t(now);
std::cout<< "Log [" << std::ctime(&time) << "]:" << message <<std::endl;
}//完美转发版本 template<typename T>auto logAndAddImpl(T&& name) -> std::enable_if_t< !std::is_convertible_v<T, int>,void >{
log("logAndAdd (perfect forwarding)");
names.emplace(std::forward<T>(name));
}//普通版本,专门处理 int 类型及其可隐式转换为 int 的类型 void logAndAddImpl(intidx) {
log("logAndAdd (int version)");//处理 int 类型的逻辑//例如,将 int 转换为字符串并添加到集合中 names.insert(std::to_string(idx));
}//分发函数 template<typename T> void logAndAdd(T&&name) {if constexpr (std::is_convertible_v<T, int>) {
logAndAddImpl(static_cast<int>(std::forward<T>(name)));
}else{
logAndAddImpl(std::forward<T>(name));
}
}//额外的非模板版本,专门处理 int 类型 void logAndAdd(intidx) {
logAndAddImpl(idx);
}intmain() {
std::string name = "Alice";int idx = 42;short idx2 = 222;//测试左值 std::cout << "Testing lvalue:\n";
logAndAdd(name);//应该调用完美转发版本//测试右值 std::cout << "\nTesting rvalue:\n";
logAndAdd(std::string("Bob")); //应该调用完美转发版本//测试 int 类型 std::cout << "\nTesting int type:\n";
logAndAdd(idx);//应该调用普通版本//测试 short 类型 std::cout << "\nTesting short type:\n";
logAndAdd(idx2);//应该调用普通版本//打印全局数据结构中的名字 std::cout << "\nNames in the global set:\n";for (const auto&name : names) {
std::cout<< name <<std::endl;
}return 0;
}
标签分发2
SFINAE (enable_if)
代码如下:

#include <iostream>#include<type_traits>#include<unordered_set>#include<chrono>#include<utility> //for std::forward, std::move> #include <string> //全局数据结构 std::unordered_set<std::string>names;//日志函数 void log(const char*message) {
auto now=std::chrono::system_clock::now();
auto time=std::chrono::system_clock::to_time_t(now);
std::cout<< "Log [" << std::ctime(&time) << "]:" << message <<std::endl;
}//完美转发版本 template<typename T>auto logAndAdd(T&& name) -> std::enable_if_t< !std::is_convertible_v<T, int>,void >{
log("logAndAdd (perfect forwarding)");
names.emplace(std::forward<T>(name));
}//普通版本,专门处理 int 类型及其可隐式转换为 int 的类型 template<typename T>auto logAndAdd(T&& idx) -> std::enable_if_t<std::is_convertible_v<T, int>,void >{
log("logAndAdd (int version)");//处理 int 类型的逻辑//例如,将 int 转换为字符串并添加到集合中 names.insert(std::to_string(static_cast<int>(idx)));
}//额外的非模板版本,专门处理 int 类型 void logAndAdd(intidx) {
log("logAndAdd (int version)");
names.insert(std::to_string(idx));
}intmain() {
std::string name = "Alice";int idx = 42;short idx2 = 222;//测试左值 std::cout << "Testing lvalue:\n";
logAndAdd(name);//应该调用完美转发版本//测试右值 std::cout << "\nTesting rvalue:\n";
logAndAdd(std::string("Bob")); //应该调用完美转发版本//测试 int 类型 std::cout << "\nTesting int type:\n";
logAndAdd(idx);//应该调用普通版本//测试 short 类型 std::cout << "\nTesting short type:\n";
logAndAdd(idx2);//应该调用普通版本//打印全局数据结构中的名字 std::cout << "\nNames in the global set:\n";for (const auto&name : names) {
std::cout<< name <<std::endl;
}return 0;
}
SFINAE
还有一种方式模板特化,就不写代码了,写的脑壳疼
总结
一入模板深似海,推荐两本书:Effective Modern C++,C++ Templates,有大佬有好的书,可以评论区推荐,感谢