weimin 发表于 2025-2-7 01:35:29

浅谈右值引用 移动语义 完美转发 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    *@briefConvert a value to an rvalue. 93    *@param__tA 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) noexcept 99   { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }   /** 66    *@briefForward 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) noexcept 74   { return static_cast<_Tp&&>(__t); } 75 76   /** 77    *@briefForward 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) noexcept 85   { 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::movetemplate<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 lvaluestemplate<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 rvaluestemplate<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::movetemplate<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 lvaluestemplate<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 rvaluestemplate<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 constructorsclass Widget {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::forwardtemplate <typename T>void forward_test(T&& arg) {    Widget w = std::forward<T>(arg);}int main() {    // 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(int idx) {    log("logAndAdd (int version)");    // 处理 int 类型的逻辑}int main() {    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(int idx) {    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(int idx) {    logAndAddImpl(idx);}int main() {    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(int idx) {    log("logAndAdd (int version)");    names.insert(std::to_string(idx));}int main() {    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,有大佬有好的书,可以评论区推荐,感谢
 
页: [1]
查看完整版本: 浅谈右值引用 移动语义 完美转发 std::move std::forward,窥探模板元编程的一角