C++11-可变参数模板

一,可变参数模板语法

🚀在C++11中提出了可变模板参数的概念,可用其创建可接受可变参数的函数模板或者类模板。

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

🚀Args是模板的参数包,args是函数形参参数包,参数包中可以包含0个或多个任意的模板参数。

🚀所以就可以像下面这样调用函数:

int main()
{
	ShowList(1, 2, "23324");
	ShowList("hello world");
	ShowList(1);
	ShowList(string("666"));
	return 0;
}

🚀可变参数模板与C语言中类似于printf的可变参数列表是不同的,可变参数模板利用的是编译器对模板的推导,实例化出多个函数或者实例化出多个类。

🚀给大家推荐一个功能十分强大的网站,借助其可以让我们看到编译器确实实例化出了多个函数或者多个类。C++ Insights
例如,对于上面的ShowList模板函数,在编译期间将会实例化出四份参数列表不同的ShowList函数。
在这里插入图片描述
🚀可以使用sizeof来计算参数包中参数的个数。

template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}
int main()
{
	ShowList(1, 2, "23324");
	ShowList("hello world");
	ShowList(1);
	ShowList(string("666"));
	return 0;
}

在这里插入图片描述

二,参数包展开

递归方式展开参数包

void ShowList()
{
	cout << endl;
}
template <typename T,typename ...Args>
void ShowList(T t,Args... args)
{
	cout << t << " ";
	ShowList(args...);
}
int main()
{
	ShowList("hello", 1, 666, "world");
	return 0;
}

🚀调用ShowList函数的时候传递了四个参数,首先将第一个参数传给了t,后面的参数传给了参数包args,(参数包中的第一个参数此时为1,又会去传给t,剩下的两个传给参数包args)依次这样递归展开下去,直到参数包为空的时候就会去调用无参的ShowList,最后终止递归。
🚀本质就是编译器递归的推导出了多了类:
在这里插入图片描述

逗号表达式展开参数包

🚀通过这种展开参数包的方式,不需要递归终止函数,是直接在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;
}

🚀 {(printArg(args), 0)…},将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组 int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分打印出参数,也就是说在创建arr数组的时候就将参数包展开了,这个数组的目的就是为了在构造数组的过程中展开参数包。

在这里插入图片描述

三,emplace_back系列

🚀在STL中,有push或insert接口在C++11后就会增加了empalce接口,容器的empalce函数是一个可变参数的函数模板,可以接受任意个数任意类型的参数。

在这里插入图片描述

在这里插入图片描述
🚀其参数采用万能引用的方式,既能接收左值也能接收右值。

emplace 与 push 比较

🚀为了更好的显示出实验的结果,采用自己实现的string类,可以将调用的对应函数名称打印出来,方便我们知晓调用了什么函数。

namespace gy
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};

	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		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;
	}
}

🚀1,传左值
在这里插入图片描述
对于传左值的情况,都是调用了一次深拷贝,二者的效率相同。

🚀2,传匿名对象的右值或move(左值)后的右值
在这里插入图片描述
对于传匿名对象的右值,push,emplace都是调用一次移动构造函数。

在这里插入图片描述

对于传move左值后的右值,push,emplace都是调用一次移动构造函数。

总结 无论是传左值还是匿名对象的右值还是move(左值)后的右值,push接口和emplace接口的效率都是相同的。

🚀3,初始化列表和直接传参

class Date
{
public:
	Date(int year = 1,int month = 1,int day = 1)
		:_year(year),_month(month),_day(day)
	{
		cout << "Date(int year = 1,int month = 1,int day = 1)" << endl;
	}
	Date(const Date& d)
		:_year(d._year),_month(d._month),_day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}
	Date(Date&& d)
		:_year(d._year), _month(d._month), _day(d._day)
	{
		cout << "Date(Date&& d)" << endl;
	}
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date& operator=(const Date& d)" << endl;
	}
	Date& operator=(Date&& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date& operator=(Date&& d)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	list<gy::string> l1;

	l1.push_back("23243");
	l1.emplace_back("23243");

	cout << "---------------------" << endl;
	list<Date> l2;

	l2.push_back({ 2023,7,2 });
	l2.emplace_back(2023,7,2);


	return 0;
}

在这里插入图片描述
对于这种传参方式,使用emplace函数是直接调用的构造函数,使用push函数是先调用的构造函数,然后再调用移动构造函数,可见使用empalce函数相比于push函数少了一次浅拷贝,如果是针对那种体积很大的浅拷贝的类而言,empalce函数是比push函数的效率略高一些的。

🚀emplace函数,在调用的过程中一直将参数包传递到创建新节点调用构造函数时,将参数包传给构造函数,在这过程中没有将参数包展开,可见emplace函数使用参数包中的参数直接调用的构造函数,比push函数少了一次浅拷贝的过程。

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大理寺j

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

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

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

打赏作者

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

抵扣说明:

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

余额充值