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

C++新增的类功能和可变参数模板

在这里插入图片描述

C++新增的类功能和可变参数模板

  • 新的类功能
    • 默认成员函数
  • 可变参数模板
  • 模拟实现emplace_back

🌏个人博客主页: 个人主页

在这里插入图片描述

新的类功能

默认成员函数

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

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

在这里插入图片描述

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

为了方便观察我们写一个简单的string

class string
{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			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)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		// s2(s1)
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			reserve(s._capacity);
			for (auto ch : s)
			{
				push_back(ch);
			}
		}

		// 移动构造
		// 临时创建的对象,不能取地址,用完就要消亡
		// 深拷贝的类,移动构造才有意义
		string(string&& s)
			:_str(nullptr)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}
	
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;

				reserve(s._capacity);
				for (auto ch : s)
				{
					push_back(ch);
				}
			}

			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{

			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);

			return *this;
		}

		~string()
		{
			cout << "~string()" << endl;
			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];
				if (_str)
				{
					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 = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};
}

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

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		,_age(age)
	{}
private:
	string _name;
	int _age;
};

int main()
{
	Person s1("Peter",18);
	Person s2 = s1;
	Person s3 = move(s1);

	return 0;
}

在这里插入图片描述

为什么这里的条件怎么苛刻呢?

因为如果一个类需要显示写析构说明有资源需要释放,那么通常就要写析构函数,拷贝构造,拷贝赋值运算重载,对资源进行管理,假如一个类是由自定义类型和内置类型构成的,对于内置类型不需要进行资源管理,只要完成值拷贝就可以了,而且要不用释放资源,对于自定义类型,如果由资源需要管理,我们只需要调用它对应写的函数即可,这样就不用我们单独写了,例如:Person类。

所以这里的条件是合理的。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。

默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		,_age(age)
	{}
private:
	string _name;
	int _age;
};

int main()
{
	Person s1("Peter",18);
	Person s2;
	s2 = s1;
	Person s3;
	s3 = move(s1);

	return 0;
}

在这里插入图片描述

如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。


class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		,_age(age)
	{}

	Person(Person&& s) = default;
private:
	string _name;
	int _age;
};

int main()
{
	Person s1("Peter",18);
	Person s2 = s1;//err

	return 0;
}

因为如果我们不写拷贝构造,编译器就会生成默认的拷贝构造,对于内置类型完成值拷贝,对应自定义类型对调用其对应的拷贝构造,但是如果我们显示写了移动构造,编译器会把移动构造当做拷贝构造的一种,就不会生成默认的拷贝构造,只能完成右值对应的拷贝。

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

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

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(Person&& p) = default;
private:
	string _name;
	int _age;
};

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

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		,_age(age)
	{}


	//Person(const Person& s) = delete;
	Person(Person&& s) = delete;
private:
	string _name;
	int _age;
};

我们只需要禁用拷贝构造或者移动构造中的一个另一个就不会自动生成,赋值重载也是同样的道理。

在这里插入图片描述

可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板。

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

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}

int main()
{
	ShowList();
	ShowList('x');
	ShowList('x', 'y');
}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。

递归函数方式展开参数包

void ShowList()
{
	cout << endl;
}

template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << val << " ";
	ShowList(args...);
}

int main()
{
	ShowList();
	ShowList('x');
	ShowList('x', 'y');
	ShowList(1,'x', 'y');
}

逗号表达式展开参数包

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('x');
	ShowList('x', 'y');
	ShowList(1,'x', 'y');
}

也可以这样写:

template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}

template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}


int main()
{
	ShowList('x');
	ShowList('x', 'y');
	ShowList(1,'x', 'y');
}

实际编译器编译推演生成了一下代码

template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}

void ShowList(int a,char b,char c)
{
	int arr[] = { PrintArg(a),PrintArg(b),PrintArg(c)};

	cout << endl;
}

int main()
{
	ShowList(1,'x', 'y');
}

STL容器中的empalce相关接口函数:

在这里插入图片描述

在这里插入图片描述

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

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么emplace系列接口的优势到底在哪里呢?

int main()
{
	list<bit::string> mylist;

	//没有区别
	string s1("1111");
	mylist.push_back(s1);
	mylist.emplace_back(s1);

	cout << endl;

	string s2("2222");
	mylist.push_back(move(s1));
	mylist.emplace_back(move(s2));

	//有区别
	cout << endl;
	//先构造临时对象 + 移动构造
	mylist.push_back("1111");
	//直接构造
	mylist.emplace_back("1111");
}

在这里插入图片描述

模拟实现emplace_back

#pragma once
#include <assert.h>

#include <iostream>

using namespace std;

namespace hb
{
	template <class T>
	struct ListNode
	{
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _data;

		ListNode(const T& data = T())
			:_prev(nullptr)
			, _next(nullptr)
			, _data(data)
		{}

		ListNode(T&& data)
			:_prev(nullptr)
			, _next(nullptr)
			, _data(move(data))
		{}

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

	template <class T,class Ref,class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		Node* _node;

		typedef ListIterator<T,Ref,Ptr> self;

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

		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;
		}

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

		bool operator!=(const self& node)
		{
			return _node != node._node;
		}

		bool operator==(const self& node)
		{
			return _node == node._node;
		}

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

	template <class T>
	struct ListConstIterator
	{
		typedef ListNode<T> Node;
		Node* _node;

		typedef ListConstIterator<T> self;

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

		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;
		}

		const T& operator*()
		{
			return _node->_data;
		}

		bool operator!=(const self& node)
		{
			return _node != node._node;
		}

		bool operator==(const self& node)
		{
			return _node == node._node;
		}

		const T* operator->()

		{
			return &_node->_data;
		}
	};


	template <class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		typedef ListIterator<T,T&,T*> iterator;
		typedef ListIterator<T,const T&,const T*> const_iterator;
		//typedef ListConstIterator<T> const_iterator;

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

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

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

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

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

		list()
		{
			empty_list();
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();

			while (it != end())
			{
				//it = erase(it);
				erase(it++);
			}
		}



		/*list(const list<T>& lt)
		{
			empty_list();

			for (const auto& e : lt)
			{
				push_back(e);
			}
		}*/

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

		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_list();//不加会出问题

			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list(const list<T>& lt)
		{
			empty_list();

			list<T> tmp(lt.begin(), lt.end());

			swap(tmp);
		}

		list<T>& operator=(list<T> tmp)
		{
			swap(tmp);

			return *this;
		}

		void push_back(const T& x)
		{
			/*Node* tail = _head->_prev;
			Node* newnode = new Node(x);

			newnode->_prev = tail;
			newnode->_next = _head;

			tail->_next = newnode;
			_head->_prev = newnode;*/

			insert(end(),x);
		}

		void push_back(T&& x)
		{
			/*Node* tail = _head->_prev;
			Node* newnode = new Node(x);

			newnode->_prev = tail;
			newnode->_next = _head;

			tail->_next = newnode;
			_head->_prev = newnode;*/

			insert(end(), move(x));
		}

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

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

		void pop_back()
		{

			erase(--end());
		}

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

		iterator insert(iterator pos, const T& x)
		{
			Node* node = pos._node;
			Node* prev = node->_prev;

			Node* newnode = new Node(x);

			newnode->_prev = prev;
			newnode->_next = node;
			
			prev->_next = newnode;
			node->_prev = newnode;

			return iterator(newnode);
		}

		template<class ...Args>
		iterator insert(iterator pos, Args&&... args)
		{
			Node* node = pos._node;
			Node* prev = node->_prev;

			Node* newnode = new Node(forward<Args>(args)...);

			newnode->_prev = prev;
			newnode->_next = node;

			prev->_next = newnode;
			node->_prev = newnode;

			return iterator(newnode);
		}

		iterator insert(iterator pos, T&& x)
		{
			Node* node = pos._node;
			Node* prev = node->_prev;

			Node* newnode = new Node(move(x));

			newnode->_prev = prev;
			newnode->_next = node;

			prev->_next = newnode;
			node->_prev = newnode;

			return iterator(newnode);
		}

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

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

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

			return iterator(next);
		}

		bool empty() const
		{
			return _head == _head->_next;
		}

		T& front()
		{
			return _head->_next->_data;
		}

		const T& front() const
		{
			return _head->_next->_data;
		}

		T& back()
		{
			return _head->_prev->_data;
		}

		const T& back() const
		{
			return _head->_prev->_data;
		}

		size_t size() const
		{
			size_t count = 0;

			Node* pcur = _head->_next;
			while (pcur != _head)
			{
				count++;
				pcur = pcur->_next;
			}

			return count;
		}

		void resize(size_t newsize, const T& data = T())
		{
			size_t oldsize = size();

			if (newsize <= oldsize)
			{
				while (newsize != oldsize)
				{
					pop_back();
					oldsize--;
				}
			}
			else
			{
				while (oldsize != newsize)
				{
					push_back(data);
					oldsize++;
				}
			}
		}
		
	private:
		Node* _head;
	};
}

代码测试:

int main()
{
	hb::list<string> mylist;

	//没有区别
	string s1("1111");
	mylist.push_back(s1);
	mylist.emplace_back(s1);

	cout << endl;

	string s2("2222");
	mylist.push_back(move(s1));
	mylist.emplace_back(move(s2));

	//有区别
	cout << endl;
	//先构造临时对象 + 移动构造
	mylist.push_back("1111");
	//直接构造
	mylist.emplace_back("1111");
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


http://www.kler.cn/news/365257.html

相关文章:

  • 机器学习——元学习(Meta-learning)
  • 【进阶OpenCV】 (19)-- Dlib库 --人脸表情识别
  • Spring Cloud:构建高可用分布式系统的利器
  • yolo自动化项目实例解析(八)自建UI-键鼠录制回放
  • 怎么提取pdf的某一页?批量提取pdf的某一页的简单方法
  • 【随便聊聊】MySQL数据类型详解:从基础到高级应用
  • filebeat收集日志直接输出到elasticsearch
  • Redis的RDB执行原理
  • MyBatis 如何映射 Enum(使用 EnumTypeHandler、自定义 TypeHandler)
  • 判断特定时间点开仓的函数(编程技巧)
  • 程序结束、脚本语言、LISP、Python
  • Ubuntu22.04 更换源
  • 在linux中 appimage是什么文件? 为什么能直接运行
  • Sqoop数据采集
  • 利用java visualvm 分析内存溢出oom
  • Oracle 第1章:Oracle数据库概述
  • nginx配置文件详解
  • Docker 部署 EMQX 一分钟极速部署
  • 数据结构之顺序表——王道
  • 为了数清还有几天到周末,我用python绘制了日历
  • 两个yaml转成的 excel对比
  • Redis3
  • 【C】数组(array)
  • PHP PDO:安全、灵活的数据持久层解决方案
  • 【ios】---SwiftUI开发从入门到放弃
  • 每日一题——第一百一十八题