浅谈右值引用 移动语义 完美转发 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,有大佬有好的书,可以评论区推荐,感谢
浅谈右值引用 移动语义 完美转发 std::move std::forward,窥探模板元编程的一角的更多相关文章
- 翻译「C++ Rvalue References Explained」C++右值引用详解 Part4:强制Move语义
本文为第四部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. 强制Move语义 众所周知,正如C++标准的第一修正案所陈述:“委 ...
- 对C++11中的`移动语义`与`右值引用`的介绍与讨论
本文主要介绍了C++11中的移动语义与右值引用, 并且对其中的一些坑做了深入的讨论. 在正式介绍这部分内容之前, 我们先介绍一下rule of three/five原则, 与copy-and-swap ...
- C++11 的右值引用
作者:Tinro链接:https://www.zhihu.com/question/22111546/answer/30801982来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
- 【C/C++开发】C++11:右值引用和转发型引用
右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如 ...
- C++ 新特性 笔记 2 右值引用
C ++ Rvalue引用说明 以下内容,主要是上述链接的摘要 介绍 Rvalue引用是C ++的一个特性,它是随C ++ 11标准添加的.使右值参考有点难以理解的是,当你第一次看到它们时,不清楚它们 ...
- C++ 11的右值引用
目录 一.问题导入 二.右值和右值引用 2.1 左值(lvalue)和右值(rvalue) 2.2 左值引用和右值引用 总结 参考资料 C++11 引入了 std::move 语义.右值引用.移动构造 ...
- 详解 C++ 左值、右值、左值引用以及右值引用
一.左值和右值 1.左值 [可以取地址的对象就是左值] 左值是一个表示数据的表达式,比如:变量名.解引用的指针变量.一般地,我们可以获取它的地址和对它赋值,但被 const 修饰后的左值,不能给它赋值 ...
- Item 25: 对右值引用使用std::move,对universal引用则使用std::forward
本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 右值引用只能绑定那些有资格被move的对象上去.如 ...
- C++ 右值引用与移动操作
右值引用和移动操作是C++11提出的新概念,通过这些操作,可以降低拷贝操作带来的消耗.先来简单介绍一下左值和右值. 左值一般指的是一个对象,或者说是一个持久的值,例如赋值的返回值.下标操作.解引用以及 ...
- 【C/C++开发】C++11:左值引用VS右值引用
左值引用VS右值引用 左值引用对于一般的C++程序员再熟悉不过,但对于右值引用(C++0X新特性),就稍微有点不知所云 左值VS右值 在定义变量的时候,经常会用到左值和右值,比如:int a = 1; ...
随机推荐
- grid网格布局
https://ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html Grid 布局只对项目生效 划分网格的线,称为"网格线" ...
- SelMatch:最新数据集蒸馏,仅用5%训练数据也是可以的 | ICML'24
数据集蒸馏旨在从大型数据集中合成每类(IPC)少量图像,以在最小性能损失的情况下近似完整数据集训练.尽管在非常小的IPC范围内有效,但随着IPC增加,许多蒸馏方法变得不太有效甚至性能不如随机样本选择. ...
- Android Qcom USB Driver学习(五)
前面的几篇都有涉及,所以本文学习一下pmic usb charger都相关的vote机制 OVP: Over Voltage Protection 过压保护 USB_IN: Input current ...
- USB 同步字段中高速同步字段和低速全速同步字段的区别
USB(Universal Serial Bus)有几种不同的传输模式:低速(Low-Speed).全速(Full-Speed).高速(High-Speed)和超级速度(SuperSpeed).同步字 ...
- Java日期时间API系列35-----Jdk8中java.time包中的新的日期时间API类应用,微秒和纳秒等更精确的时间格式化和解析。
通过Java日期时间API系列1-----Jdk7及以前的日期时间类中得知,Java8以前除了java.sql.Timestamp扩充纳秒,其他类最大只精确到毫秒:Java8 time包所有相关类都支 ...
- 数据库周刊60丨3月国产数据库排行榜出炉;日本银行数据迁移失败致使业务宕机;阿里云RDS PG13发布;亚健康Oracle数据库故障定位;Redis最佳实践;MySQL查询优化……
热门资讯 1.2021年3月国产数据库排行榜:雏凤声清阿里三连 绝代双骄华为合璧 [摘要]2021年3月国产数据库流行度排行榜已出炉,在本月排行的前十名中,TiDB 仍然以领先第二名135分 的优势稳 ...
- 比赛题解 更新ING
CF Codeforces Round #750 (Div. 2) (水题.组合数学.双指针+搜索.构造.dp.dp) Codeforces Round #747 (Div. 2) (水题.水题.埃筛 ...
- KubeSphere 社区双周报 | Fluent Operator 2.6.0 发布 | 2023.11.10-11.23
KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...
- 云原生周刊:Istio 1.19 发布 | 2023.9.11
开源项目推荐 Timoni Timoni 是 Kubernetes 的软件包管理器,由 CUE 提供支持,灵感来自 Helm. Timoni 项目致力于改善编写 Kubernetes 配置的用户体验. ...
- 开源函数计算平台 OpenFunction 保姆级入门教程
OpenFunction 0.6.0 上周已经正式发布了,带来了许多值得注意的功能,包括函数插件.函数的分布式跟踪.控制自动缩放.HTTP 函数触发异步函数等.同时,异步运行时定义也被重构了.核心 A ...