浅谈右值引用 移动语义 完美转发 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) noexcept
 99     { 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) noexcept
 74     { 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) 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::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
class 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::forward
template <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,有大佬有好的书,可以评论区推荐,感谢

相关推荐
PluviophileDD2 小时前
【笔记】C语言转C++
c语言·c++
獨枭2 小时前
MFC 自定义编辑框:打造灵活的数据输入控件
c++·mfc
安於宿命2 小时前
【Linux】文件系统
linux·服务器·c++
oioihoii3 小时前
C++ 中 std::array<int, array_size> 与 std::vector<int> 的深入对比
开发语言·c++
OTWOL3 小时前
预处理基础指南
开发语言·数据结构·c++·算法
老猿讲编程3 小时前
【零成本抽象】汽车嵌入式软件开发中零成本抽象的功能安全考量与应对策略
c++·安全·汽车·零成本抽象
ZPILOTE4 小时前
日志基础示例python和c++
c++·python·日志·log·logger·glog
ZLRRLZ4 小时前
【C++】继承
c++
codears4 小时前
Qt编写的文件传输工具
c++·qt