首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【c++11】可变参数模版

【c++11】可变参数模版

作者头像
用户11029103
发布2025-01-24 08:04:21
发布2025-01-24 08:04:21
3130
举报
文章被收录于专栏:技术学习技术学习

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

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

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个(三个都没实现)。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
代码语言:javascript
复制
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	/*Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
	Person& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}
	~Person()
	{}*/
private:
	myown::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}
在这里插入图片描述
在这里插入图片描述

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

代码语言:javascript
复制
Person(Person&& p) = default;

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

代码语言:javascript
复制
Person(const Person& p) = delete;

该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

2.可变参数模版

C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进

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

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

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

递归展开参数包

代码语言:javascript
复制
// 递归终止函数
template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " ";
	ShowList(args...);
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

递归终止函数

代码语言:javascript
复制
template <class T>
void ShowList(const T& t)
{
    cout << t << endl;
}

这个函数模板是递归终止函数。当可变参数包被展开到只剩一个参数时,调用这个函数来处理最后一个参数,并打印出它的值。

展开函数

代码语言:javascript
复制
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
    cout << value <<" ";
    ShowList(args...);
}

这是递归展开的函数模板。该函数处理当前第一个参数 value 并打印,然后通过递归调用自身来处理余下的参数包 args...

ShowList(1, 'A', std::string("sort"))**

  • ShowList(1, 'A', std::string("sort"))
    • 调用展开函数模板,处理第一个参数 1,递归调用 ShowList('A', std::string("sort"))
      • ShowList('A', std::string("sort")) 调用展开函数,处理第一个参数 A,递归调用 ShowList(std::string("sort"))
        • ShowList(std::string("sort")) 调用终止函数,输出:sort
      • 输出:A sort
    • 输出:1 A sort
代码语言:javascript
复制
1
1 A
1 A sort

逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式

代码语言:javascript
复制
template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——列表初始化,通过列表初始化来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... )最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包

代码语言:javascript
复制
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	std::cout << std::endl;
}

ShowList 函数中,int arr[] = { (PrintArg(args), 0)... }; 语句是关键所在:

  • (PrintArg(args), 0)... 是一个逗号表达式。它会对参数包 args 中的每个参数展开,并依次调用 PrintArg 函数打印每个参数。
  • 逗号表达式返回的最终值是 0,这就是每个展开的元素分别是 (PrintArg(args), 0), 确保每个元素最终被转换为 0 并存储在 int 数组中。

每次调用 ShowList 都会根据提供的参数数量和类型进行相应地展开并打印。

  • 逗号表达式 (PrintArg(args), 0)... 允许我们对每个参数进行操作,这在展开可变参数包时非常有用。
  • 初始化列表 { ... } 用来收集所有展开的结果。
  • 使用 (void) 强制类型转换可以避免编译器发出警告。
  • 该方法使处理不定数目参数的模板函数变得简洁而高效。

3.emplace_back

代码语言:javascript
复制
template <class... Args>
void emplace_back (Args&&... args);

emplace_back 是 C++ 标准库容器(例如 std::vector, std::deque 等)的一个成员函数,它的主要作用是直接在容器末尾构造一个元素,而不是先构造一个临时对象再拷贝或移动到容器中。


代码语言:javascript
复制
container.emplace_back(arguments...);
  • arguments... 是传递给元素构造函数的参数。
  1. 直接构造emplace_back 直接在容器的内存中调用元素的构造函数。
  2. 支持可变参数:你可以传递多个参数,这些参数将直接传递给对象的构造函数。

避免意外构造emplace_back 直接调用构造函数,因此会发生隐式类型转换。如果构造函数的参数可以匹配多个重载,可能导致意外的构造。

代码语言:javascript
复制
std::vector<int> numbers;
numbers.emplace_back(3.14); // 会将 3.14 隐式转换为 int,并存储为 3

需要构造函数支持emplace_back 需要对应的构造函数存在。如果类没有合适的构造函数,emplace_back 会无法使用。

这一部分区别不大

emplace_back是直接构造了,push_back是先构造,再移动构造

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2.可变参数模版
  • 3.emplace_back
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档