当前位置: 首页 > news >正文

C++11部分内容(中)

注:C++学习之路.cpp: 每天更新创库有着要用的比较长的list与string的简单的底层代码,而且String.h包含了要用的头文件,下面的代码例子我就只包括main()函数

一.引用折叠

1.特点:

<1>.C++中不能直接定义引用的引用如 int& && r = i; ,这样写会直接报错,通过模板或typedef 中的类型操作可以构成引用的引用。

<2>. 通过模板或typedef中的类型操作可以构成引用的引用时,这时C++11给出了⼀个引用折叠的规 则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用。

2.例子

下面的程序中很好的展示了模板和typedef时构成引用的引用时的引用折叠规则,⼤家需要⼀个⼀ 个仔细理解⼀下。

2.1.显示实例化

例1.typedef

int main() { typedef int& lref; typedef int&& rref; int n = 0; lref& r1 = n; // r1 的类型是int& lref&& r2 = n; // r2 的类型是int& rref& r3 = n; // r3 的类型是int& rref&& r4 = 1; // r4 的类型是int&& return 0; }

例2.模板

大框架:

// 由于引⽤折叠限定,f1实例化以后总是⼀个左值引⽤ template<class T> void f1(T& x) { int a = 10; T b = a; cout << &a << endl; cout << &b << endl; } // 由于引⽤折叠限定,f2实例化后可以是左值引⽤,也可以是右值引⽤ template<class T> void f2(T&& x) { int a = 10; T b = a; cout << &a << endl; cout << &b << endl; } int main() { int n = 10; ... return 0; }

main函数内:

//没有折叠->实例化为void f1(int& x),T为int f1<int>(n); //f1<int>(0); 报错,0为右值,无法传参

输出结果:

//折叠引用->实例化为void f1(int& x),T为int& f1<int&>(n); //f1<int&>(0); 报错,0为右值,无法传参

输出结果:

//折叠->实例化为void f1(const int& x),T为const int& f1<const int&>(n); f1<const int&>(0); //const引用可以正常引用右值

输出结果:

// 折叠->实例化为void f1(const int& x),T为const int&& f1<const int&&>(n); f1<const int&&>(0); //const引用可以正常引用右值

注:此时要将T b = a;和cout << &b << endl;注释掉

输出结果:

//没有折叠->实例化为void f2(int&& x),T为int //f2<int>(n); // 报错,右值引用不可以引用左值,除非move(左值) f2<int>(0);

输出结果:

//折叠->实例化为void f2(int& x),T为int& f2<int&>(n); //f2<int&>(0); // 报错,0为右值,无法传参

输出结果:

// 折叠->实例化为void f2(int&& x),T为int&& //f2<int&&>(n); // 报错 f2<int&&>(0);

注:此时要将T b = a;和cout << &b << endl;注释掉

输出结果:

2.2.隐式实例化

大框架:

template<class T> void Function(T&& t) { int a = 0; T x = a; //x++; cout <<"a的地址:" << &a << endl; cout <<"x的地址:" <<&x << endl << endl; } int main() { ... return 0; }

main函数内:

int main() { // 10是右值,推导出T为int,模板实例化为void Function(int&& t); Function(10); int a; // a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t) Function(a); // a是右值,但是std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t) Function(std::move(a)); const int b = 8; // b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int&t) // 所以Function内部会编译报错,x不能++ Function(b);// const 左值 // std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t) // 所以Function内部会编译报错,x不能++ Function(std::move(b)); //const 右值 return 0; }

输出结果:

3.完美转发

特点:

<1>.Function(T&&t)函数模板程序中,传左值实例化以后是左值引用的Function函数,传右值实例化以后是右值引用的Function函数。

<2>. 结合我们之前的讲解,变量表达式都是左值属性,也就意味着⼀个右值被右值引用绑定 后,右值引用变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传递给下⼀层函数Fun,那么匹配的都是左值引用版本的Fun函数。

例如:

void Fun(int& x) {cout << "左值引用" << endl; } void Fun(const int& x) {cout << "const 左值引用" << endl; } void Fun(int&& x) {cout << "右值引用" << endl; } void Fun(const int&& x) {cout << "const 右值引用" << endl; } template<class T> void Function(T && t){Fun(t);} int main() { // 10是右值,推导出T为int,模板实例化为void Function(int&& t) Function(10); int a; // a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t) Function(a); // std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t) Function(std::move(a));// 右值 const int b = 8; // b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int& t) Function(b); // const 左值 // std::move(b)右值,推导出T,const int,模板实例化为void Function(const int&&t) Function(std::move(b)); // const 右值 return 0; }

输出结果:

这里我们想要保持t对象的属性, 就需要使用完美转发实现。

完美转发forward本质是⼀个函数模板,他主要还是通过引用折叠的方式实现,下面示例中传递给 Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引用返回;传递给 Function的实参是左值,T被推导为int&,引用折叠为左值引用,forward内部t被强转为左值引用返回。

那么我们将Fun(t);改为Fun(forward<T>(t));那么就会发生变化。

输出结果:

4.完美转发对string的改变

有了右值引用和引用折叠后,我们可以将原来List的底层改变一下,例如将insert改变为:

iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(x); // prev newnode cur newnode->_next = cur; cur->_prev = newnode; newnode->_prev = prev; prev->_next = newnode; ++_size; return newnode; } iterator insert(iterator pos, T&& x) { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(forward<T>(x)); // prev newnode cur newnode->_next = cur; cur->_prev = newnode; newnode->_prev = prev; prev->_next = newnode; ++_size; return newnode; }

这样以后,当传kong::string类左值时就会调用拷贝构造,当传kong::string类右值时就会调用移动构造,这样就可以提高效率,但是这样就显得十分的繁琐,但是完美转发可以解决这个问题。

将其改为:

template<class X> iterator insert(iterator pos, X&& x) { //cout << "segwgsegw" << endl; Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(forward<X>(x)); //cout << "qwdqdqa" << endl; // prev newnode cur newnode->_next = cur; cur->_prev = newnode; newnode->_prev = prev; prev->_next = newnode; ++_size; return newnode; }

当然其他的也要改变,完整的代码就在无忧.芙桃/C++学习之路.cpp - 码云 - 开源中国这里面。

二.可变参数模板

1.基本语法及原理

特点:

<1>.C++11支持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数目的参数被称 为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表示零或多个函 数参数。

例如:

template <class ...Args> void Func(Args... args) {} template <class ...Args> void Func(Args&... args) {} template <class ...Args> void Func(Args&&... args) {}

<2>.我们用...(省略号)来指出⼀个模板参数或函数参数的表示⼀个包,在模板参数列表中,class.../typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...指出 接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板 ⼀样,每个参数实例化时遵循引用折叠规则

<3>.可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数

<4>.这里我们可以使用sizeof...运算符去计算参数包中参数的个数。

例如:

template<class...Args> void Print(Args&&...args) { cout << sizeof...(args) << endl; } int main() { double x = 2.2; Print(); // 包⾥有0个参数 Print(1); // 包⾥有1个参数 Print(1, string("xxxxx")); // 包⾥有2个参数 Print(1.1, string("xxxxx"), x); // 包⾥有3个参数 return 0; }

原理1:编译本质这里会结合引用折叠规则实例化出以下四个函数:
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string && arg2);
void Print(double&& arg1, string && arg2, double&,arg3);

原理2:更本质去看没有可变参数模板,我们实现出这样的多个函数模板去支持实现,这里的功能,有了可变参数模板,我们进⼀步被解放,他是类型泛化基础上叠加数量变化,让我们泛型编程更灵活,而下面的编译器多个参数模板:
void Print();

template <class T1>
void Print(T1 && arg1)

template <class T1, class T2>
void Print(T1 && arg1, T2 && arg2);

template <class T1, class T2, class T3>
void Print(T1 && arg1, T2 && arg2, T3 && arg3)

2.包扩展

特点:

<1>.对于⼀个参数包,我们除了能计算他的参数个数,我们还可以做的事就是扩展它,当扩展⼀个 包时,我们还要提供用于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。

例如:

void ShowList() { cout << endl; } template<class T,class...Args> void ShowList(T x, Args...args) { cout << x << " "; ShowList(args...); } template <class ...Args> void Print(Args... args) { ShowList(args...); } int main() { Print(); Print(1); Print(1, string("xxxxx")); Print(1, string("xxxxx"), 2.2); return 0; }

输出结果:

底层的实现细节如图所示:

本质编译器将可变参数模板通过模式的包扩展,编译器推导的以下几个重载函数函数

void ShowList() { cout << endl; } void ShowList(double z) { cout << z << " "; ShowList(); } void ShowList(string y, double z) { cout << y << " "; ShowList(z); } void ShowList(int x, string y, double z) { cout << x << " "; ShowList(y, z); } void Print(int x, string y, double z) { ShowList(x, y, z); }

<2>. C++还支持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。

3.empalce系列接口

特点:

<1>.C++11以后STL容器新增了empalce系列的接口,empalce系列的接口均为模板可变参数,功能上兼容push和insert系列,但是empalce还支持新玩法,假设容器为container,empalce还支持直接插入构造T对象的参数,这样有些场景会更高效⼀些,可以直接在容器空间上构造T对象。

例如使用库里的list的emplace_back:

int main() { list<bit::string> lt; bit::string s1("111111111111"); bit::string s2("111111111111"); // 传左值,跟push_back一样,走拷贝构造 lt.emplace_back(s1); cout << "*********************************" << endl; lt.push_back(s1); cout << "*********************************" << endl; // 右值,跟push_back一样,走移动构造 lt.emplace_back(move(s1)); cout << "*********************************" << endl; lt.push_back(move(s2)); cout << "*********************************" << endl; lt.emplace_back("111111111111"); cout << "*********************************" << endl; // 直接传参,隐式类型转换 lt.push_back("111111111111"); cout << "*********************************" << endl; return 0; }

输出结果:

问:为什么emplace_back一会走拷贝构造,一会走构造,一会又走移动构造?

解释:s1是一个左值,类型是kong::string,编译器看到左值,触发引用折叠规则,把Args&& 推导为左值引用,而底层节点构造,代码变成了new Node(kong:string&),kong::string的构造函数中,能接收s1的左值引用的,只有拷贝构造函数string(const string&);走移动构造的过程也相似;传入"111111111111"时,b编译器会认为这是一个字符串常量,类型是const char*,类型推导时,编译器把 Args&&推导为const char*(严格来说是常数组引用,这里简化为指针),底层节点构造时,代码变成了new Node(const char*),所以它它匹配到了普通带参构函数。

如果使用我们自己的emplace_back函数的话,

int main() { bit::list<pair<bit::string, int>> lt1; cout << "*********************************" << endl; // 跟push_back一样 // 构造pair + 拷贝/移动构造pair到list的节点中data上 pair<bit::string, int> kv("苹果", 1); cout << "*********************************" << endl; lt1.emplace_back(kv); cout << "*********************************" << endl; // 21:15 // 跟push_back一样 lt1.emplace_back(move(kv)); cout << "*********************************" << endl; // 这里达到的效果是push_back做不到的 //lt1.emplace_back({ "苹果", 1 }); // 不支持 lt1.emplace_back("苹果", 1 ); // cout << "*********************************" << endl; lt1.push_back({ "苹果", 1 }); //里面的万能引用无法识别{ "苹果", 1 }的类型为pair,要使用原来的push_back cout << "*********************************" << endl; return 0; }

输出结果:

<2>. emplace_back总体而言是更高效,推荐以后使用emplace系列替代insert和push系列

注意:传递参数包过程中,如果是 Args&&... args 的参数包,要用完美转发参数包,方式如下 std::forward(args)... ,否则编译时包扩展后右值引用变量表达式就变成了左值。

http://www.cnnetsun.cn/news/1302828.html

相关文章:

  • JavaWeb-Vue基础
  • Abaqus焊接仿真培训资料:涵盖热源模型、子程序与应力应变场数值模拟全解
  • 告别依赖烦恼:在Windows上通过MSYS2一站式部署MRtrix3
  • CV实战:Harris角点检测在图像拼接中的应用(Python+OpenCV实现)
  • Phi-3-vision-128k-instruct企业级落地:制造业设备铭牌识别与信息抽取案例
  • 无线通信关键参数解析:从dB到RSRP的实战应用指南
  • 0.96寸ST7735驱动IPS彩屏在STM32上的移植与驱动详解
  • NEURAL MASK 在Web开发中的应用:构建一个在线老照片修复平台
  • 具身智能学习路线
  • Lychee-Rerank企业面试系统应用:Java八股文智能匹配
  • 实时手机检测-通用高性能部署:共享内存IPC优化多进程并发检测吞吐
  • RyzenAdj完全掌控指南:释放AMD锐龙处理器的终极性能潜力
  • HDU:杭电 2019 复试真题汇总
  • 基于麻雀搜索优化算法优化最小二乘支持向量机(SSA-LSSVM)的多输出数据回归预测 SSA-...
  • 基于ESP32与ESP-ADF框架:三合一智能音箱(蓝牙/网络电台/AI对话)DIY全流程解析
  • JiYuTrainer实战通关:从原理到应用的零门槛之旅
  • 技术突破:让旧Mac重获新生的极限释放指南
  • Hotkey Detective:一站式解决Windows热键冲突问题
  • 3步完成Magma智能体部署:Linux系统环境配置全指南
  • 裂隙相控制方程
  • Qwen3-14b_int4_awq多轮对话效果展示:Chainlit界面中上下文保持与逻辑连贯性案例
  • OPENPPP2静态隧道UDP中断问题排查与解决
  • 立创EDA实战:基于ESP8266的智能温控杯架《暖男杯架》开源项目全解析
  • 实战指南:用Docker快速搭建Canal+MySQL+Kafka数据同步环境(附避坑技巧)
  • 从网络IO到高并发Reactor模式:吃透网络库设计核心逻辑
  • 4个步骤掌握WebPlotDigitizer高效图表数据提取
  • Chrome无法上网,但其他浏览器正常
  • 突破动画迁移瓶颈:mixamo_converter实现Mixamo到Unreal Engine 4根骨骼动画的无缝转换
  • 【后端必看】什么是 Elasticsearch?都要学什么?
  • Chromium WebRTC 架构解析:从信令协商到媒体传输的实现原理