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

c++进阶之----c++11(可变参数模板)

1.基本概念

可变参数模板允许模板函数或模板类接受零个或多个参数。这些参数可以是不同类型的数据。其语法的核心是使用 ...(省略号)来表示可变参数部分。

 2.语法

template <typename... Args>
void func(Args... args);
  • typename... Args:表示参数类型列表,Args 是一个类型参数包(parameter pack)。

  • args...:表示参数值列表,args 是一个值参数包(parameter pack)。

  • 使用sizeof...运算符去计算参数包中参数的个数。

 3.完美转发

完美转发是指在模板函数中,能够将参数以完全相同的值类别(lvalue 或 rvalue)传递给目标函数。std::forward 是实现完美转发的关键工具。

3.1为什么需要完美转发?

在模板函数中,参数的类型可能是一个左值引用或右值引用。如果不使用 std::forward,直接将参数传递给目标函数可能会导致类型不匹配或性能问题。

  • 如果直接传递参数,左值引用会被当作左值处理,右值引用会被当作右值处理。

  • 但目标函数可能需要以不同的方式处理左值和右值。

3.2 std::forward 的工作原理

std::forward 的行为取决于模板参数 T 的类型:

  • 如果 T 是一个左值引用类型(T&),std::forward 会将参数作为左值返回。

  • 如果 T 是一个右值引用类型(T&&),std::forward 会将参数作为右值返回。

  • 如果 T 是一个非引用类型(T),std::forward 会根据传入的参数类型决定返回左值还是右值。

 3.3 引用折叠

在C++中,引用折叠是C++11标准引入的一个特性,用于处理引用的引用(例如 T&&)时的类型推导规则。引用折叠的目的是确保引用的引用不会导致复杂的、难以理解的类型,而是会折叠成一个简单的引用类型。它在模板编程和完美转发中非常重要。

3.3.1 引用折叠的规则

引用折叠的规则如下:

  • 两个左值引用T& & 折叠成 T&

  • 左值引用和右值引用T& && 折叠成 T&

  • 右值引用和左值引用T&& & 折叠成 T&

  • 两个右值引用T&& && 折叠成 T&&

  • 总结:左+左=左;左+右=左;右+右=右

3.3.2 代码训练

 判断一下代码哪些会报错(ps:先不要看注释,自己做,结合引用折叠的规则判断函数被实例化成什么,在根据左值右值判断正确与否

// 由于引⽤折叠限定,f1实例化以后总是⼀个左值引⽤
#include<iostream>
using namespace std;
template<class T>
void f1(T& x)
{}

// 由于引⽤折叠限定,f2实例化后可以是左值引⽤,也可以是右值引⽤
//如果传入的参数是左值,T&&会折叠为T&,即左值引用。
//如果传入的参数是右值,T&& 保持为T&& ,即右值引用。
template<class T>
void f2(T&&x)
{}

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&&


	//先通过f1f2的定义判断出被实例化成啥,在判断对错

	// 没有折叠->实例化为void f1(int& x)
	f1<int>(n);    //因为 n 是一个左值(假设 n 是一个 int 类型的变量),它可以绑定到 int& 上。
	//f1<int>(0);   这是因为 0 是一个整型字面量,它是一个右值,而 f1<int> 被实例化为 void f1(int& x),它期望的是一个 int 类型的左值引用。在C++中,你不能将一个右值绑定到左值引用上

	// 折叠->实例化为void f1(int& x)
	f1<int&>(n);
	//f1<int&>(0); // 报错//因为 0 是一个右值,不能绑定到 int& 上。

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

	// 折叠->实例化为void f1(const int& x)
	//字面量可以绑定到 const 左值引用上
	f1<const int&>(n);
	f1<const int&>(0);

	// 折叠->实例化为void f1(const int& x)
	f1<const int&&>(n);
	f1<const int&&>(0);


	// 没有折叠->实例化为void f2(int&& x)
	//f2<int>(n);
	f2<int>(0);

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

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

	return 0;
}

3.4 万能引用

万能引用是一种特殊的引用类型,它在模板参数推导时可以匹配任何类型(左值或右值)。它使用 && 来声明,但是它的行为取决于模板参数的推导结果。

示例:

// 万能引用
template<class T>
void function(T&& t)
{
	int a = 0;
	T x = a;
	//x++;

	cout << &a << endl;
	cout << &x << endl << endl;
}
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(move(a));     右值

	const int b = 8;
	// b是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int&t)
	// const 限定的变量不能被修改,所以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;
}

 我们可以用forward优化一下

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)
{
	// 保持t的属性
	Fun(forward<T>(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;
	// a是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int& t)
	Function(b);

	// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
	Function(std::move(b)); // const 右值

	return 0;
}

4. emplace接口

在C++中,emplace系列接口是一组非常有用的函数,它们允许你直接在容器内部构造对象,而不是先构造一个临时对象然后再将其插入容器中。这可以提高效率,特别是当对象的构造函数比较复杂或者需要多个参数时。

4.1 主要特点

  1. 原地构造emplace系列函数直接在容器内部构造对象,避免了额外的复制或移动操作。

  2. 可变参数模板:这些函数通常与可变参数模板一起使用,以便于直接在容器内部就地拷贝对象,而不是通过拷贝构造函数或移动构造函数进行操作。

  3. 性能优化:在某些情况下,使用emplace系列函数可以显著提升性能,尤其是当插入的对象较复杂时。

  4. 代码简洁emplace提供了更简洁的语法,直接传递构造函数的参数,无需显式地创建对象。

  5. 构造方式:传左值,跟push_back一样,走拷贝构造;传右值,跟push_back一样,走移动     构造

  6. 使用方式emplace系列接口的使用方式与容器原有的插入接口的使用方式类似,但又有一些不同之处。例如,emplace_back函数可以接受用于构造元素的参数包。不要再写{}了!!!而push_back()要写,要走initializer_list

 

std::vector<std::pair<int, std::string>> mylist;
mylist.emplace_back(1, "A");

 在这个例子中,我们直接传递了构造std::pair<int, std::string>所需的参数,而不是先创建一个pair对象再将其插入到vector中。

4.2 与push_back的比较

4.2.1 push_back

当使用 push_back 时,如果容器需要更多的空间来存储新元素,可能会触发重新分配内存的操作,这涉及到复制或移动现有元素到新的内存位置,然后添加新元素。push_back 通常需要一个已经构造好的对象作为参数,或者使用拷贝构造函数或移动构造函数来构造新元素。

4.2.2 emplace_back

emplace_back 是 C++11 引入的方法,它允许你直接在容器内部构造新元素,而不需要先构造一个临时对象。使用 emplace_back 可以传递多个参数,这些参数将直接用于构造新元素,从而避免了额外的复制或移动操作。emplace_back 通常用于需要传递多个参数或避免临时对象的场合,特别是在处理大型对象或资源密集型对象时,可以显著提高性能。

4.2.3 性能比较

在大多数情况下,emplace_backpush_back 更有效率,因为它避免了创建和销毁临时对象的开销。当容器需要重新分配内存时,emplace_backpush_back 的性能差异可能不那么明显,因为重新分配的开销占主导地位。

4.2.4 使用场景

使用 push_back: 当你已经有一个构造好的对象,或者当你需要添加的对象的构造函数非常简单时。

使用 emplace_back :当你需要传递多个参数来构造新元素,或者当你希望避免创建临时对象以提高性能时。

相关文章:

  • 学习计划:从MCP入门到项目构建的全面指南
  • Vue.js 中 v-show 的使用及其原理
  • C++ -异常之除以 0 问题(整数除以 0 编译时检测、整数除以 0 运行时检测、浮点数除以 0 编译时检测、浮点数除以 0 运行时检测)
  • 0.机器学习基础
  • SpringBoot整合MinIO快速入门:实现分布式文件存储与管理
  • QTSql全解析:从连接到查询的数据库集成指南
  • LCR 056. 两数之和 IV - 输入二叉搜索树
  • 工业4.0时代,RK3562工控机为何成为智慧工位首选?
  • PostgreSQL的内存管理机制
  • 《Operating System Concepts》阅读笔记:p587-p596
  • 弹簧质点系统(C++实现)
  • 平均标准差策略思路
  • CExercise_07_1指针和数组_1编写函数交换数组中两个下标的元素
  • 谈谈模板方法模式,模板方法模式的应用场景是什么?
  • LLM+js实现大模型对话
  • 判断矩阵A是否可以相似对角化
  • MySQL 在 CentOS 7 环境安装完整步骤
  • 任务调度和安全如何结合
  • WinMerge下载及使用教程(附安装包)
  • AnimateCC基础教学:随机抽取花名册,不能重复
  • 东洋学人|滨田青陵:近代日本考古学第一人
  • 陕西永寿4岁女童被蜜蜂蜇伤致死,当地镇政府介入处理
  • 湖北十堰市委副秘书长管聪履新丹江口市代市长
  • 我国科研团队发布第四代量子计算测控系统
  • 女租客欠租后失联,房东开门后无处下脚:40平公寓变垃圾场
  • 江西浮梁县县长张汉坤被查,此前已有4个月无公开活动