爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      【C++】C++11——右值引用和移动语义|可变参数模板

      首页 知识中心 软件开发 文章详情页

      【C++】C++11——右值引用和移动语义|可变参数模板

      2024-05-29 09:01:43 阅读次数:43

      c++,开发语言

      一、左值引用和右值引用

      传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

      左值引用和右值引用的定义

      左值 是一个表示数据的表达式 (如变量名或解引用的指针),我们可以对左值取地址,也可以对左值赋值 (const 左值不能赋值); 左值既可以出现在赋值符号的左边,也可以出现在赋值符号的右边;左值引用 就是给左值的引用,给左值取别名。

      int main()
      {
      	//以下的a、b、c均为左值
      	int* a = new int(0);
      	int b = 1;
      	const int c = 2;
      
      	// 左值引用给左值取别名
      	int& ref1 = a;
      	int& rb = b;
      	return 0;
      }
      

      右值 也是一个表示数据的表达式,如字面常量、表达式返回值、函数返回值等等,右值可以出现在赋值符号的右边,但不能出现出现在赋值符号的左边,右值不能取地址。

      常见的右值和右值引用:

      int main()
      {
      	int a, b, x, y;
      
      	//常见的右值
      	10;
      	x + y;
      	fmin(x, y);
      
      	// 左值引用给左值取别名
      	int& ref1 = a;
      
      	// 左值引用给右值取别名
      	const int& ref2 = (a + b);
      
      	// 右值引用给右值取别名
      	int&& ref3 = (a + b);
      
      	// 右值引用给move后左值取别名
      	//int&& ref4 = a;
      	int&& ref4 = move(a);
      
      	return 0;
      }
      

      需要注意的是:

      • 为什么函数返回值是右值: 当函数返回的是一个局部变量时,因为局部变量出了函数生命周期就会结束,所以返回时会将该变量拷贝到寄存器中,然后返回这个寄存器中的内容,而寄存器中的变量是临时变量,临时变量具有常性,属于右值。
      • 为什么右值不能取地址: 在 C++中,右值则是一个临时使用的、不可寻址的内存值;右值没有独立的内存空间,它只是存储在寄存器或其他临时内存空间中的一个值;我们也不能把右值放入内存中,因为右值没有确定的内存位置,所以右值不能取地址。

      注意: 虽然右值不能取地址,但是给右值取别名后,会导致右值被存储到特定位置,拥有独立的内存空间,所以可以取到该位置的地址;换句话来说,虽然右值引用引用的是右值,但右值引用本身是一个左值,所以如果我们不希望改变右值引用,我们就需要将右值引用定义为 const 右值引用。

      【C++】C++11——右值引用和移动语义|可变参数模板


      左值引用和右值引用的比较

      • 左值引用不能直接引用右值,但是 const 左值引用可以引用右值,因为 const 左值引用也是只读的,而权限可以平移:
      int main()
      {
      	// 左值引用只能引用左值,不能引用右值。
      	int a = 10;
      	int& ra1 = a;
      	//int& ra2 = 10;  // 编译失败,因为10是右值
      
      	// const左值引用既可引用左值,也可引用右值。
      	const int& ra3 = 10;
      	const int& ra4 = a;
      	return 0;
      }
      
      • 右值引用也不可以直接引用左值,但是右值引用可以引用 move 后的左值:
      int main()
      {
      	// 右值引用只能右值,不能引用左值。
      	int&& r1 = 10;
      	// error C2440: “初始化”: 无法从“int”转换为“int &&”
      	int a = 10;
      	//int&& r2 = a;
      
      	// 右值引用可以引用move以后的左值
      	int&& r3 = std::move(a);
      
      	return 0;
      }
      

      二、右值引用的使用场景和意义

      左值引用的短板

      我们先来看一下左值引用可以解决的问题:

      • 做参数:a、减少拷贝,提高效率。b、做输出型参数。
      • 做返回值:a、减少拷贝,提高效率。b、引用放回,可以修改返回对象(比如:operator[ ])

      左值引用既可以引用左值又可以引用右值,那为什么C++11还要提出右值引用呢,其实左值引用无法解决一些场景问题,所以就提出了右值引用。

      函数返回对象是一个局部变量时,就不能使用左值引用返回,而只能传值返回了,因为局部对象出了函数作用域就不存在了,此时引用的就是一个野指针;如下:

      //左值引用的短板——不能解决局部对象的返回值问题
      template <class T>
      T func1(const T& x) {
      	T tmp;
      	//...
      
      	return tmp;  //出这个函数tmp会自动销毁
      }
      

      这种情况下下编译器会使用这个局部对象拷贝构造一个临时对象,然后再返回这个临时对象,也就是说,会比引用返回多一次拷贝构造;当局部对象是一个需要进行深拷贝的自动类型时,比如 vector<vector>,拷贝构造的代价就很大了。而右值引用的提出就是为了补足左值引用存在的这些短板的。


      移动构造和移动赋值

      假设我们要在自己实现的string类中实现一个 to_string 函数,如下:

      cjl::string to_string(int value)
      {
      	bool flag = true;
      	if (value < 0)
      	{
      		flag = false;
      		value = 0 - value;
      	}
      
      	cjl::string str;
      	while (value > 0)
      	{
      		int x = value % 10;
      		value /= 10;
      
      		str += ('0' + x);
      	}
      
      	if (flag == false)
      	{
      		str += '-';
      	}
      
      	std::reverse(str.begin(), str.end());
      	return str;
      }
      

      由于 to_string 函数返回的 str 是一个局部对象,所以这里我们只能使用传值返回,而传值返回就需要进行深拷贝。

      【C++】C++11——右值引用和移动语义|可变参数模板

      正常情况下应该是 两个拷贝构造 (多出来一次构造是 to_string 函数内部构造 str);但是我们发现这里只有一次拷贝构造。这其实是因为当遇到连续构造的场景时编译器会进行优化,直接使用 str 来拷贝构造得到 s,而不再创建临时对象。

      但是优化只适用于少数场景,大部分情况下还是会拷贝构造产生临时对象,比如:

      【C++】C++11——右值引用和移动语义|可变参数模板

      为了将 str 的资源直接转移给 s,中间不发生拷贝构造,我们就可以使用右值引用来发挥这个功能了。

      C++11 中的右值广义的来说一共分为两种:

      • 纯右值: 内置类型表达式的值;
      • 将亡值: 自定义类型表达式的值;所谓的将亡值就是指生命周期马上就要结束的值,一般来说匿名对象、临时对象、move 后的自定义类型都可以看做是将亡值。

      注: 上面我们说右值不能取地址其实是右值的严格定义,但其实将亡值也是可以被当作右值看待的,而将亡值有独立的内存空间,可以取地址;所以对于是否是右值我们要灵活看待。

      既然将亡值的生命周期马上就要结束了,那么在拷贝构造中我们就可以直接将将亡值的资源拿过来给我自己使用,这样我就不用再去一个一个 new 节点了,将亡值也不用去一个一个释放节点了,两全其美。现在,我们重载一个右值引用版本的构造函数 – 移动构造,这样当实参类型为右值的对象需要进行拷贝构造时就会调用此函数;在函数中,我们直接拿走将亡值的资源,从而使得深拷贝变为了浅拷贝,显著提高了程序的效率。

      // 移动构造
      string(string&& s)
      	:_str(nullptr)
      {
      	cout << "string(string&& s) -- 移动拷贝" << endl;
      	swap(s);
      }
      

      【C++】C++11——右值引用和移动语义|可变参数模板
      【C++】C++11——右值引用和移动语义|可变参数模板

      本来这里 str 会先拷贝构造一个临时对象,由于临时对象属于右值,所以会直接调用移动拷贝来构造 s;但是这里编译器进行了优化,直接将 str 识别为右值,让它来移动构造 s,所以通过移动构造 (右值引用) 我们成功将深拷贝变为了浅拷贝。

      这里我们需要注意的是:

      只有当实参为右值时才会匹配 移动构造构造函数进行优化,当实参为左值时编译器在匹配参数还是会匹配形参为 const T& 的拷贝构造函数;因为编译器不知道我们是否还会对左值进行操作,所以它不敢拿走左值的资源来构造新的对象。

      💕 移动赋值

      和移动构造同理,只是移动赋值中将亡值还需要释放掉我之前的资源,不过这个过程是自动的:

      //移动赋值
      string& operator=(string&& s)
      {
          cout << "string& operator=(string&& s) -- 移动语义" << endl;
          swap(s);
          return *this;
      }
      

      网上有的人说右值引用延长了变量的生命周期,这种说法其实是不准确的;因为右值引用只是将该变量的资源转移给另外一个变量,让它的资源能够不随着该变量的销毁而被释放,而该变量本身的生命周期是没有变的。

      总结:

      • 左值引用 让形参成为实参的别名,直接减少拷贝;
      • 右值引用 通过实现移动构造和移动赋值,将将亡值的资源进行转移,间接减少拷贝。(浅拷贝的类不需要进行资源转移,所以也就没有移动赋值和移动拷贝)

      STL中的容器也都是增加了移动构造和移动赋值的,同时,STL容器的插入接口函数也增加了右值引用的版本。


      万能引用和完美转发

      万能引用 是一个 函数模板,且函数的形参类型为右值引用;对于这样的函数模板,编译器能够自动根据实参的类型 – 左值/ const 左值/ 右值/ const 右值,自动推演实例化出不同的形参类型分别为 左值引用/ const 左值引用/ 右值引用/ const 右值引用 的函数;如下:

      //万能引用
      template<typename T>
      void PerfectForward(T&& t)
      {
      	//fun(t);
      }
      
      int main()
      {
      	int a;
      	const int b = 8;
      	PerfectForward(a);	//左值
      	PerfectForward(b);	//const 左值
      	PerfectForward(10);	//右值
      	PerfectForward(std::move(b));  //const 右值
      
      	return 0;
      }
      

      不管实参为什么类型,模板函数都能正确接受并实例化为对应的引用类型,所以我们把形参为右值引用的函数模板叫做万能引用。其中,当实参为左值或 const 左值时,T&& 会被实例化为 T& 或 const T&,我们称其为 引用折叠,即 将 && 折叠为 &。


      💕 完美转发

      我们上面讲解了万能引用,但是万能引用存在一个很大的问题: 万能引用实例化后函数的形参的属性全部都是左值 – 如果实参为左值/ const 左值,则实例化函数的形参是左值/ const 左值;如果实参是右值/ const 右值,虽然实例化函数的形参是右值引用/ const 右值引用,但是右值引用本身是左值;所以就会出现下面这种情况:

      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<typename T>
      void PerfectForward(T&& t)
      {
      	Fun(t);
      }
      
      int main()
      {
      	PerfectForward(10);           // 右值
      
      	int a;
      	PerfectForward(a);            // 左值
      	PerfectForward(std::move(a)); // 右值
      
      	const int b = 8;
      	PerfectForward(b);		      // const 左值
      	PerfectForward(std::move(b)); // const 右值
      
      	return 0;
      }
      

      【C++】C++11——右值引用和移动语义|可变参数模板

      为了在传参的过程中能够保留对象原生类型属性,C++11 又设计出了完美转发 – forward

      【C++】C++11——右值引用和移动语义|可变参数模板
      【C++】C++11——右值引用和移动语义|可变参数模板

      总结:C++11 的右值引用之旅:

      • 旅程一:为了弥补左值引用局部对象返回会发生拷贝构造的问题,C++11 设计出了右值引用;右值引用可以通过移动构造和移动赋值实现资源转移,将深拷贝转化为浅拷贝,从而提高程序效率,这是 C++11 中非常重要的一个设计;
      • 同时,C++11 还为 STL 中的容器都提供了右值版本的插入接口,但由于右值引用本身是左值,所以往下一层传递时不能保证其仍然是右值,所以C++11 又设计出了 move,但盲目的对左值进行 move 会导致错误。
      • 旅程二:为了让模板函数能同时接受 (const) 左值和 (const) 右值并正确实例化为对应的引用类型,C++11 又设计出了万能引用,附带的又引出了引用折叠这个概念;但是这样奇怪的设计让许多学习 C++11 的人苦不堪言。
      • 旅程三:万能引用的设计又带来了新的问题 – 不管是左值引用还是右值引用,其本身都是左值,所以往下一层传递时又要面对类型丢失的问题,但是这里使用之前的 move 已经不能解决问题了,所以 C++11 又又又设计出了完美转发,来保证传参的过程中对象原生类型属性能够保持不变。

      三、新的类功能

      原来C++类中,有6个默认成员函数:

      1. 构造函数
      2. 析构函数
      3. 拷贝构造函数
      4. 拷贝赋值重载
      5. 取地址重载
      6. const 取地址重载

      最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
      C++11 新增了两个:移动构造函数和移动赋值运算符重载。

      针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

      • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
        意一个。
        那么编译器会自动生成一个默认移动构造。 默认生成的移动构造函数,对于内置类
        型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
        如果实现了就调用移动构造,没有实现就调用拷贝构造
        。
      • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
        的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
        置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
        值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
        完全类似)。
      • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

      我们在讲解新的类的功能之前先看一个重要的问题,情况如下图所示:

      【C++】C++11——右值引用和移动语义|可变参数模板

      • 情况一: 虽然 const& 会延长对象的生命周期,但是当我们返回局部对象的const&时,由于出了该函数的作用域,函数栈帧就会销毁,同时该对象所在的空间的数据就会被释放,这块空间已经被归还给操作系统了,但是我们却还要使用它给拷贝给新创建的对象,因此导致非法访问内存。程序奔溃。因此const& 会延长局部对象的生命周期仅仅是在同意作用域中起作用。出了作用域,无论如何该对象都会被销毁。
      • 情况二: 这次我们依然返回局部对象的引用,但是这次我们使用一个const&来接受,由于const& 依然是给返回的局部对象的引用取别名,因此不会去操作内存,但是该局部对象所在的栈帧已经被销毁了, 当执行下一条语句时,会开辟新的栈帧,新的栈帧中的地址可能会占用该局部对象所在的地址,因此该地址就会被占用,所以该地址中的内容可能会被篡改,导致s1变成了随机值。

      【C++】C++11——右值引用和移动语义|可变参数模板

      当我们不返回局部对象的引用时,并且使用引用来接受该值时则不会出现任何问题,因为编译器做优化后会在该函数return之前将str识别为将亡值,先将该对象移动拷贝一份,然后再释放掉该对象,这样该对象的资源就会被转移走了,然后在外面使用const& 来接收该返回值(移动拷贝后的值)完全没有问题。

      下面我们来看一下新的类功能:

      class Person
      {
      public:
      	Person(const char* name = "", int age = 0)
      		:_name(name)
      		, _age(age)
      	{}
      
      private:
      	cjl::string _name; // 自定义类型
      	int _age = 1;		   // 内置类型
      };
      

      【C++】C++11——右值引用和移动语义|可变参数模板

      写了析构函数后:

      【C++】C++11——右值引用和移动语义|可变参数模板

      简单来说,如果你什么都没有实现,或者只实现了一个构造函数,那么编译器会自动生成移动拷贝和移动赋值;自动生成的对于内置类型完成值拷贝,对于自定义类型看自定义类型是否实现了移动构造或移动赋值,实现了就调用自定义类型的移动构造或移动赋值,没有实现就调用自定义类型拷贝构造和赋值重载。


      类成员变量初始化

      C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这
      个我们在前面类和对象的博客默认就讲了,这里就不再细讲了

      【C++】C++11——右值引用和移动语义|可变参数模板


      default 和 delete

      强制生成默认函数的关键字default:

      由于默认移动构造和移动赋值函数的生成条件十分苛刻,所以 C++11 提供了 default 关键字,它可以显示指定生成某个默认成员函数;比如我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成;如下:

      【C++】C++11——右值引用和移动语义|可变参数模板

      禁止生成默认函数的关键字delete:

      假如我们要设计一个类,它不允许被拷贝,传统的做法是将拷贝构造函数定义为私有函数,但这种做法只防止了在类外进行拷贝,而在类内我们仍然可以调用拷贝构造函数完成拷贝,此时编译器在编译时不会发生错误,只有运行起来对同一块空间析构两次时才会报错;

      class A {
      public:
      	A() {
      		_ptr = new int[10]{ 0 };
      	}
      
      	~A() {
      		delete[] _ptr;
      	}
      
      	//在类内进行拷贝
      	void func() {
      		A tmp(*this);
      		//...
      	}
      
      private:
      	//将拷贝构造定义为私有,防止在类外进行拷贝
      	A(const A& a)
      		: _ptr(a._ptr)
      	{}
      
      	int* _ptr;
      };
      
      int main()
      {
      	A a;
      	a.func();
      	return 0;
      }
      

      【C++】C++11——右值引用和移动语义|可变参数模板

      那么我们如何才能让一个类既不能在外部被拷贝,也不能在内部被拷贝呢? 其实我们可以只给出拷贝构造函数的声明,且声明为私有;这样,只要调用了拷贝构造函数,那么在链接时一定会发生错误:

      【C++】C++11——右值引用和移动语义|可变参数模板

      C++11 中提供了一种更为便捷的方法 —— 在函数声明加上 =delete 即可,delete 关键字可以阻止函数的自动生成,我们称被 =delete 修饰的函数为删除函数;如下:

      【C++】C++11——右值引用和移动语义|可变参数模板

      注意:default 关键字都只能针对默认成员函数使用;而 delete 关键字既可以对默认成员函数使用,也可以对非默认成员函数和普通函数使用。


      四、可变参数模板

      C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止,以后大家如果有需要,再可以深入学习。

      下面就是一个基本可变参数的函数模板

      // Args是一个模板参数包,args是一个函数形参参数包
      // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
      template <class ...Args>
      void ShowList(Args... args)
      {}
      

      上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数 包”, 它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能 通过展开参数包的方式来获取参数包中的每个参数 ,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变
      参数,所以我们的用一些奇招来一一获取参数包的值。

      💕 计算可变参数的个数:

      //可变参数的模板
      template<class ...Args>
      void ShowList(Args...args)
      {
      	cout << sizeof...(args) << endl;
      }
      int main()
      {
      	string str("hello");
      	ShowList();
      	ShowList(1);
      	ShowList(1, 'A');
      	ShowList(1, 'A', str);
      	return 0;
      }
      

      【C++】C++11——右值引用和移动语义|可变参数模板

      当我们看到可变参数模板时,很自然会联想到使用如下方法来依次取出参数包中的每个参数:

      template <class ...Args>
      void ShowList(Args... args)
      {
      	//求参数包中参数的个数
      	cout << sizeof...(args) << endl;
      
      	//依次取出参数包中的每个参数--error
      	for (int i = 0; i < sizeof...(args); i++) {
      		cout << args[i] << endl;
      	}
      }
      

      虽然上面这种方法非常好理解,但是C++11标准中并不允许以这种方式来取出参数包中的参数,而是使用另外两种非常晦涩的方式来完成,如下:

      💕 递归函数方式展开参数包

      将参数包中的第一个参数赋值给 val,将剩下的 n-1 个参数以类似于递归子问题的方式逐个取出,当参数包为空时再调用最后一次,至此将参数包中的参数全部取出;

      //递归终止函数
      void ShowList()
      {
      	cout << "ShowList()" << endl;
      }
      
      //展开函数,参数包args包含N个参数(N>=0)
      template<class T, class...Args>
      void ShowList(const T& val, Args...args)
      {
      	cout << "ShowList(" << val << ",参数包args有" << sizeof...(args) << "个参数)" << endl;
      	ShowList(args...); // -- 递归调用
      }
      
      int main()
      {
      	string str("hello");
      	ShowList(1, 'A', str);
      	return 0;
      }
      

      【C++】C++11——右值引用和移动语义|可变参数模板

      💕 逗号表达式方式展开参数包

      这种方式是利用了数组初始化的特性,我们在用0初始化数组时需要知道列表中参数的个数,而参数的个数需要通过展开参数包获得。

      可以看到,C++11 提供的这两种参数包展开的方式比起 args[i] 这种方式真的是晦涩太多了,特别是逗号表达式展开,但是没办法,语言就是这么规定的;不过也不用太在这里纠结,参数包展开能看懂就行,我们并不需要去深究它的底层原理。

      //逗号表达式展开参数包
      template <class T>
      void PrintArg(const T& val)
      {
      	cout << val << " ";
      }
      
      //展开函数
      template<class...Args>
      void ShowList(Args...args)
      {
      	int arr[] = { (PrintArg(args), 0)... };
      	cout << endl;
      }
      
      int main()
      {
      	string str("hello world");
      	ShowList(1);
      	ShowList(1, 'A');
      	ShowList(1, 'A', str);
      	return 0;
      }
      

      【C++】C++11——右值引用和移动语义|可变参数模板

      template <class T>
      int PrintArg(T t)
      {
      	cout << t << " ";
      
      	return 0;
      }
      
      template <class ...Args>
      void ShowList(Args... args)
      {
      	int arr[] = { PrintArg(args)... };
      	cout << endl;
      }
      

      【C++】C++11——右值引用和移动语义|可变参数模板


      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://chenjiale.blog.csdn.net/article/details/131383045,作者:星河万里᭄ꦿ࿐,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:【C++】list的使用 | 模拟实现

      下一篇:程序员如何提升自己

      相关文章

      2025-05-19 09:04:53

      查看RISC-V版本的gcc中默认定义的宏

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53
      c++ , linux
      2025-04-14 09:26:51

      【算法入门08】青蛙跳台阶

      【算法入门08】青蛙跳台阶

      2025-04-14 09:26:51
      c++ , 动态规划 , 算法
      2025-04-14 09:26:51

      STL详解(九)—— priority_queue的使用与模拟实现

      优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

      2025-04-14 09:26:51
      c++ , stl , 数据结构
      2025-04-14 09:26:51

      STL详解(八)—— stack和queue的模拟实现

      stack和queue有一点需要注意的是,虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和queue只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque容器。

      2025-04-14 09:26:51
      c++ , queue , stack , stl
      2025-04-14 09:24:23

      【算法入门14】二叉树的镜像

      【算法入门14】二叉树的镜像

      2025-04-14 09:24:23
      c++ , 算法
      2025-04-14 09:24:23

      【算法入门09】矩形覆盖

      【算法入门09】矩形覆盖

      2025-04-14 09:24:23
      c++ , 动态规划 , 算法
      2025-04-07 10:28:48

      Python高维统计建模变量选择:SCAD平滑剪切绝对偏差惩罚、Lasso惩罚函数比较

      变量选择是高维统计建模的重要组成部分。许多流行的变量选择方法,例如 LASSO,都存在偏差。

      2025-04-07 10:28:48
      python , r语言 , 后端 , 开发语言
      2025-04-07 10:20:39

      ​Python是如何表示时间的?2个模块、3种方式,1文看懂~

      ​Python是如何表示时间的?2个模块、3种方式,1文看懂~

      2025-04-07 10:20:39
      python , 开发语言 , 时间戳 , 结构化
      2025-04-01 10:29:12

      golang与 C++数据结构类型对应关系是怎样的?

      uintptr和unsafe.Pointer相当于c++的void*,也就是任意指针。

      2025-04-01 10:29:12
      c++ , golang , 函数指针 , 数据结构
      2025-04-01 10:28:37

      找到非负数组中拥有“最大或的结果“的最短子数组,返回最短长度。

      找到非负数组中拥有"最大或的结果"的最短子数组,返回最短长度。

      2025-04-01 10:28:37
      java代码 , rust , 后端 , 开发语言 , 数组
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5236441

      查看更多

      最新文章

      Python高维统计建模变量选择:SCAD平滑剪切绝对偏差惩罚、Lasso惩罚函数比较

      2025-04-07 10:28:48

      ​Python是如何表示时间的?2个模块、3种方式,1文看懂~

      2025-04-07 10:20:39

      golang与 C++数据结构类型对应关系是怎样的?

      2025-04-01 10:29:12

      找到非负数组中拥有“最大或的结果“的最短子数组,返回最短长度。

      2025-04-01 10:28:37

      【C语言】探索数据的存储(上篇)

      2025-04-01 09:21:49

      MFC编程 -- 判断是否按下ctrl和shift键

      2025-03-31 08:49:25

      查看更多

      热门文章

      Lambda函数

      2023-02-08 10:33:56

      QT中多线程的使用

      2023-02-07 10:34:04

      0030 简单的四则运算 c/c++

      2023-03-21 10:39:47

      C++虚函数知识点总结

      2023-02-21 06:21:46

      (10)Qt对象模型

      2023-02-13 07:55:59

      java学习第三天笔记-运算符10-短路逻辑运算符56

      2023-04-07 06:41:50

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      C/C++ Qt 信号自定义槽函数

      Shell 判断文件或文件夹是否存在(不存在则创建)

      C/C++创建tty,创建终端

      C++ 字符串 处理 消除多余空格

      返回一个数组中,所有降序三元组的数量。

      Python 为url每个参数赋值Payload(为域名每个参数赋值Payload)

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号