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

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;//errreturn 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.mrgr.cn/news/58643.html

相关文章:

  • 遇到一个python pexpect的坑 scp -rp拷贝文件不成功
  • java中this的内存原理是?
  • 【Qt6聊天室项目】 主界面功能实现
  • 1024是什么日子
  • 跨界创新|使用自定义YOLOv11和Ollama(Llama 3)增强OCR文本识别
  • 信息安全工程师(66)入侵阻断技术与应用
  • 一文带你彻底吃透GO的context到底是个啥?
  • 回顾复习1:
  • leetcode-75-颜色分类
  • 解读 Java 经典巨著《Effective Java》90条编程法则,第2条:遇到多个构造器参数时要考虑使用构建器
  • 使用js-enumerate报错Cannot set properties of undefined
  • 前端零基础入门到上班:【Day4】HTML 多媒体与表单深度教程
  • 创建型模式-----建造者模式
  • 为什么磁链的基准值ψB=UB*tB
  • 用人工智能,应该怎么掏钱?
  • frida脚本,自动化寻址JNI方法
  • 【C++】一文带你深入理解C++异常机制
  • 7款视频转换器大测评!哪款是最适合你的视频格式转换器?
  • 现代Web界面交互新利器!来探一探这个魔法组件库——MagicUI
  • python 访问openai assistant api(一)
  • 金融工程--pine-script 入门
  • 【python实战】利用代理ip爬取Alibaba海外版数据
  • 从视频中学习的SeeDo:通过VLM解释视频并生成规划、代码(含通过RGB视频模仿的人形机器人OKAMI、DexMV)
  • 机器学习与神经网络的当下与未来
  • Vue学习记录之二十一 Vue3中3种编程风格介绍
  • 凯伦股份荣获中国钢结构协会2024年度技术创新奖