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

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

1.新的类功能

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

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

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

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

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

Person(Person&& p) = default;

在这里插入图片描述
禁止生成默认函数的关键字delete

Person(const Person& p) = delete;

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

2.可变参数模版

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 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;
}
  1. 递归终止函数

    template <class T>
    void ShowList(const T& t)
    {
        cout << t << endl;
    }
    

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

  2. 展开函数

    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
1
1 A
1 A sort

逗号表达式展开参数包

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

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数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包

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

template <class... Args>
void emplace_back (Args&&... args);

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


container.emplace_back(arguments...);
  • arguments... 是传递给元素构造函数的参数。
  1. 直接构造emplace_back 直接在容器的内存中调用元素的构造函数。

  2. 支持可变参数:你可以传递多个参数,这些参数将直接传递给对象的构造函数。


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

    std::vector<int> numbers;
    numbers.emplace_back(3.14); // 会将 3.14 隐式转换为 int,并存储为 3
    
  2. 需要构造函数支持emplace_back 需要对应的构造函数存在。如果类没有合适的构造函数,emplace_back 会无法使用。

在这里插入图片描述

这一部分区别不大

在这里插入图片描述
emplace_back是直接构造了,push_back是先构造,再移动构造

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QuiteCoder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值