【C++】C++11:可变模板参数、emplace

C++11:可变模板参数、emplace

1、可变模板参数的概念

C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,例如:

cpp 复制代码
template <class ...Args> void Func(Args... args) {}
template <class ...Args> void Func(Args&... args) {}
template <class ...Args> void Func(Args&&... args) {}

上面的参数中Args是一个模板参数包,args是一个函数形参参数包,声明一个参数包Args... args,这个参数包中可以包含0到任意个模板参数

可变参数模板就是支持任意数量、任意类型参数,参数包是用...来打包一堆参数

我们无法直接获取参数包args中的每个参数,语法不支持使用argsi这样的方式获取可变参数,只能通过展开参数包的方式来获取参数包中的每个参数

2、获取参数包中参数的个数

cpp 复制代码
template <class ...Args>
void Print(Args&&... args)
{
	cout << sizeof...(args) << endl;
}

int main()
{
	double x = 2.2;

	Print(); // 包里有0个参数
	Print(1); // 包里有1个参数
	Print(1, string("xxxxx")); // 包里有2个参数
	Print(1.1, string("xxxxx"), x); // 包里有3个参数

	return 0;
}

输出结果:

0

1

2

3

可以通过sizeof... (args)来查看参数包中的参数个数

3、递归函数方式展开参数包

cpp 复制代码
// 递归终止函数
void ShowList()
{
	cout << endl;  //所有参数打印完,换行收尾
}

// 每次拆分出第一个参数T x,剩下的打包成Args... args
template <class T, class ...Args>
void ShowList(T x, Args... args)
{
	// 先打印当前拆出来的第一个参数
	cout << x << " ";

	// 将剩下没打印的args包继续传给自己,递归拆解
	ShowList(args...);
}

// 接收任意个任意类型参数包,转发给ShowList处理
template <class ...Args>
void Print(Args... args)
{
	ShowList(args...);
}

int main()
{
	double x = 2.2;

	Print(); // 包里有0个参数
	Print(1); // 包里有1个参数
	Print(1, string("xxxxx")); // 包里有2个参数
	Print(1.1, string("xxxxx"), x); // 包里有3个参数

	return 0;
}

输出结果:

1

1 xxxxx

1.1 xxxxx 2.2

该方法是通过递归调用ShowList函数去获取参数包中的参数,每递归一次就可以从参数包中取出一个参数存到形参val中。当参数包把最后一个参数传递给ShowList(T x, Args... args) 后,参数包中没有任何东西,相当于无参调用ShowList() ,所以写了上面的递归终止函数,这样就可以把参数包中的最后一个参数提取出来,并结束递归

在编译器底层:

5、emplace系列接口

5.1 push_back的用法

下面使用模拟实现的list配合string演示push_back和emplace_back的用法与差异

cpp 复制代码
int main()
{
	MyList::list<MyList::string> lt;
	// 传左值,跟push_back一样,走拷贝构造
	MyList::string s1("111111111111");
	lt.emplace_back(s1);
	cout << "\n" << endl;

	// 右值,跟push_back一样,走移动构造
	lt.emplace_back(move(s1));
	cout << "\n" << endl;

	// 直接把构造string参数包往下传,直接用string参数包构造string
	// 这里达到的效果是push_back做不到的
	lt.emplace_back("111111111111"); // 构造
	lt.emplace_back(10, 'x');        // 构造
	lt.push_back("111111111111");    // 构造+移动构造
	cout << "\n" << endl;

	// 链表中存日期类对象
	//lt.emplace_back(2025, 1, 1);  // 构造
	//lt.push_back({ 2025, 1, 1 }); // 构造+拷贝构造
	
	MyList::list<pair<MyList::string, int>> lt1;
	// 跟push_back一样
	// 构造pair + 拷贝/移动构造pair到list的节点中data上
	pair<MyList::string, int> kv("苹果", 1);
	lt1.emplace_back(kv);
	cout << "\n" << endl;

	// 跟push_back一样
	lt1.emplace_back(move(kv));
	cout << "\n" << endl;

	////////////////////////////////////////////////////////////////////
	// 直接把构造pair参数包往下传,直接用pair参数包构造pair
	// 这里达到的效果是push_back做不到的
	lt1.emplace_back("苹果", 1);
	// lt1.emplace_back({ "苹果", 1 }); 不支持,形参是模板,无法推导形参类型
	lt1.push_back({ "苹果", 1 }); // 隐式类型转换传参
	cout << "\n" << endl;

	return 0;
}

输出结果:

由上面的代码可以发现,如果使用push_back传递构造pair的参数,那么会产生构造和移动构造,如果使用emplace_back传递构造pair的参数,那么只会产生一次构造,可以提高效率

5.2 emplace_back使用方法

cpp 复制代码
template <class... Args>
void emplace_back (Args&&... args);

emplace_back和push_back最大的区别是push_back的参数类型是固定的,而emplace_back采用的是可变模板参数,push_back的三种使用方式同样适用于emplace_back

list中的应用

cpp 复制代码
template <class... Args>
void emplace_back(Args&&... args)
{
	emplace(end(), forward<Args>(args)...);
}

// 优化后的insert
template <class... Args>
void emplace(iterator pos, Args&&... args)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(forward<Args>(args)...);

	// prev newnode cur
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;

	++_size;
}

// 改造构造函数
template <class... Args>
list_node(Args&&... args)
	: _next(nullptr)
	, _prev(nullptr)
	, _data(forward<Args>(args)...)
{
}

普通insert,必须先造好完整pair对象再插入:

cpp 复制代码
bit::list<pair<string, int>> lt;
lt.insert(lt.begin(), pair<string,int>("苹果",1));

emplace函数:

cpp 复制代码
lt.emplace(it, "苹果", 1);

emplace_back新增的用法:

cpp 复制代码
lt.emplace_back(10, 'x');

使用函数:

cpp 复制代码
template <class... Args>
void emplace_back(Args&&... args)
{
	emplace(end(), forward<Args>(args)...);
}

emplace_back 直接传构造参数,原地构造,无临时

push_back只能传完整对象,必须先生成临时string

cpp 复制代码
lt.push_back(string(10, 'x'));

6、list改良后代码

cpp 复制代码
#pragma once
#pragma once

#include<assert.h>

namespace MyList
{
	template<class T>
	struct list_node
	{
		list_node* _next;
		list_node* _prev;
		T _data;

		list_node(const T& x)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{
		}

		list_node(T&& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(move(x))
		{
		}

		template <class... Args>
		list_node(Args&&... args)
			: _next(nullptr)
			, _prev(nullptr)
			, _data(forward<Args>(args)...)
		{
		}
	};

	// typedef list_iterator<T, T&, T*> iterator;
	// typedef list_iterator<T, const T&, const T*> const_iterator;
	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;
		Node* _node;

		list_iterator(Node* node)
			:_node(node)
		{
		}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;

			return tmp;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;

			return tmp;
		}

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}
	};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		typedef list_iterator<T, T&, T*> iterator;
		//typedef list_const_iterator<T> const_iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			_size = 0;
		}

		list()
		{
			empty_init();
		}

		list(initializer_list<T> il)
		{
			empty_init();
			for (auto& e : il)
			{
				push_back(e);
			}
		}

		// lt2(lt1)
		//list(const list<T>& lt)
		list(list<T>& lt)
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		// lt1 = lt3
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		size_t size()
		{
			return _size;
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		// 不是万能引用
		// T是通过实参传递推导的吗?不是,T是list实例化的
		void push_back(T&& x)
		{
			//insert(end(), move(x));
			insert(end(), (T&&)x);
		}

		// 万能引用
		//template<class X>
		//void push_back(X&& x)
		//{
		//	// 保持他的属性,往下传递,左值引用就是保持左值属性,右值引用就保持右值属性
		//	insert(end(), forward<X>(x));
		//}

		//template<class X>
		//void insert(iterator pos, X&& x)
		//{
		//	Node* cur = pos._node;
		//	Node* prev = cur->_prev;
		//	Node* newnode = new Node(forward<X>(x));

		//	// prev newnode cur
		//	prev->_next = newnode;
		//	newnode->_prev = prev;
		//	newnode->_next = cur;
		//	cur->_prev = newnode;

		//	++_size;
		//}

		template <class... Args>
		void emplace_back(Args&&... args)
		{
			emplace(end(), forward<Args>(args)...);
		}

		template <class... Args>
		void emplace(iterator pos, Args&&... args)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(forward<Args>(args)...);

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;
		}

		void insert(iterator pos, T&& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(move(x));
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;

			--_size;

			//return iterator(next);
			return next;
		}
	private:
		Node* _head;
		size_t _size;
	};
相关推荐
郝学胜-神的一滴1 小时前
中级OpenGL教程 030:gl_FragCoord解锁区域渲染与深度可视化新姿势
c++·unity·游戏引擎·图形渲染·unreal engine·opengl
不会代码的小猴2 小时前
11. 类和动态内存分配
开发语言·c++
鱼子星_2 小时前
【C++】stack和queue的应用及其模拟实现:适配器与容器适配器
数据结构·c++·笔记·stl
jufeng13073 小时前
【系列:MiniKV 原理剖析 · 第 2 篇】
linux·c++·软件工程
charlie1145141913 小时前
IMX6ULL WM8960 的移植——前置介绍
开发语言·c++·开源项目·嵌入式linux
charlie1145141914 小时前
Cinux是如何管理进程的 —— 上下文与调度
开发语言·c++·操作系统·开源项目
hold?fish:palm4 小时前
redis中AOF 重写机制解析
数据库·c++·redis
阿米亚波4 小时前
【C++ 异常处理】try-catch
开发语言·c++·笔记·try-catch
旖旎夜光4 小时前
C++(内存管理)
开发语言·c++·学习
皓月斯语4 小时前
P2858 [USACO06FEB] Treats for the Cows G/S
数据结构·c++·算法·动态规划