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

C++11新增可变参数模板

C++11新增可变参数模板

  • c语言的可变参数
  • c++11新增可变参数模板及递归展开参数包
  • 逗号表达式展开参数包
  • STL中的emplace接口
  • 为容器添加emplace_back

c语言的可变参数

在c语言,scanfprintf的形参就是一个可变参数。可变参数可以接受几乎无限个参数。

c语言阶段,使用可变参数:

  1. 需要展开stdarg.h头文件。
  2. 声明函数时给一个固定参数,用于确定可变参数的起始位置。
  3. 使用va_list类型定义一个变量,用于访问可变参数列表。
  4. 使用va_start初始化va_list,绑定到固定参数。
  5. 使用va_arg按照指定类型读取va_list的参数。
  6. 使用va_end清理va_list

例如定义一个能对任意数量的数据求和的函数:

#include<stdio.h>
#include<stdarg.h>int sum(int cnt, ...) {int ans = 0;va_list args;va_start(args, cnt);for (int i = 0; i < cnt; i++)ans += va_arg(args, int);va_end(args);return ans;
}int main() {printf("%d\n", sum(2, 2, 3));//第1个实参表示多少个参数printf("%d", sum(3, 2, 3,4));return 0;
}

再例如模拟printf的c语言代码。

#include <stdio.h>
#include <stdarg.h>// 自定义的printf风格函数
void my_printf(const char* format, ...) {va_list args;va_start(args, format);//初始化可变参数列表vprintf(format, args);//使用vprintf输出va_end(args);//清理可变参数列表
}int main() {my_printf("Hello, %s! The answer is %d.\n", "world", 42);return 0;
}

关于可变参数如何实现,以后有机会再整理细节。

c++11新增可变参数模板及递归展开参数包

C++11新增的新特性中有可变参数模板,通过它可以创建接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。

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

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

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

参数args前面有省略号,所以它就是一个可变模版参数,通常把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。

若使用sizeof计算可变参数包的大小,只能得到参数个数,而且还需要按照这个格式获取:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;template <class ...Args>
void ShowList(Args... args) {cout << sizeof...(args) << endl;//输出上传的形参个数
}int main() {ShowList("sekai", 3.14, 2.71);return 0;
}

语法设计上无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以需要用一些奇招来一一获取参数包的值,比如编译时的递归推演。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;// 递归终止函数
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(2, 'A', std::string("sort"));ShowList("sekai", 3.14,2.71);return 0;
}

参数包可以为0,所以也能这样:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;// 递归终止函数
void ShowList() {//可变参数包内的参数为0cout << endl;
}// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args) {cout << value << " ";ShowList(args...);
}int main() {ShowList(1);ShowList(1, 'A');ShowList(2, 'A', std::string("sort"));ShowList("sekai", 3.14, 2.71);return 0;
}

逗号表达式展开参数包

通过逗号表达式这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。

我们知道逗号表达式会按顺序执行逗号前面的表达式。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数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;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(2, 'A', std::string("sort"));ShowList("sekai", 3.14, 2.71);return 0;
}

但本质都是将参数包内的所有参数作为形参调用函数PrintArg,因此可以直接让PrintArg返回0,来初始化数组arr即可。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;template <class T>
int PrintArg(T t) {cout << t << " ";return 0;
}
//展开函数
template <class ...Args>
void ShowList(Args... args) {//这种做法是让编译器强行解析参数包int arr[] = { PrintArg(args)... };//args有多少参数,就调用多少次PrintArgcout << endl;
}int main() {ShowList(1);ShowList(1, 'A');ShowList(2, 'A', std::string("sort"));ShowList("sekai", 3.14, 2.71);return 0;
}

STL中的emplace接口

STL的大多数容器都添加了emplaceemplace就用到了可变参数,还支持万能引用。例如emplace_back尾插:

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

后文用到的 mystd.h,string(添加有移动构造和移动赋值):

#pragma once
#include<string>
#include<iostream>
#include<vector>
#include<cstdlib>
#include<typeinfo> 
#include<cstring>
#include<cassert>
#include<algorithm>
using std::vector;
using std::string;
using std::cout;
using std::endl;
using std::reverse;
using std::forward;namespace mystd {class string {public:typedef char* iterator;iterator begin() {return _str;}iterator end() {return _str + _size;}string(const char* str = ""):_str(nullptr), _size(strlen(str)), _capacity(_size) {cout << "string(char* str) 构造\n";_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), _size(0), _capacity(0) {cout << "string(string& s)拷贝构造\n";string tmp(s._str);swap(tmp);}// 移动构造string(string&& s):_str(nullptr), _size(0), _capacity(0) {cout << "string(string&& s)移动构造\n";swap(s);}// 赋值重载string& operator=(const string& s) {cout << "string& operator=(string&s) 赋值重载\n";if (this != &s) {char* tmp = newchar[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}//移动赋值string& operator=(string&& s) {cout << "string& operator=(string&& s) 移动赋值\n";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};//因为ret是局部对象,不能传引用返回。//string& to_string(int x) mystd::string to_string(int x) {mystd::string ret;while (x) {int val = x % 10;x /= 10;ret += ('0' + val);}reverse(ret.begin(), ret.end());return ret;}
}

emplace_back支持可变参数,若容器存储的数据类型是自定义类型,则拿到构建对象的参数后自己去创建对象插入容器。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<list>
#include"mystd.h"
using namespace std;int main() {//先查看库里的用法std::list<std::pair<mystd::string,int> >lt;lt.emplace_back("12345",1);//可变参数接受完所有参数就构造pair//lt.emplace_back("asdfg",1,"hjkl",2);//可变参数也不接受多余参数cout << endl;lt.push_back(make_pair("6789", 2));//需先构造pair再上传return 0;
}

1个参数时在用法上,和push_back没什么太大的区别。真正的区别是emplace_back拿完参数后直接构造,而push_back要先构造string对象(隐式类型转换),再移动构造。

但移动构造本身成本就很低,所以在库中emplace_backpush_back的优势也只是少调用一次或几次构造函数,影响不是特别明显。

为容器添加emplace_back

emplace_back需要有支持万能引用的构造函数才能通过new(args...)生成新的数据。

例如自制的list,向其中添加的emplace_back和支持万能引用的list结点构造函数:

//支持万能引用的list结点构造函数
template<class... Args>
list_node(Args&&... args):_data(args...), _next(nullptr), _prev(nullptr) {}//自制emplace
template <class... Args>
void emplace_back(Args&&... args) {//直接生成数据的原理Node* newnode = new Node(args...);// 链接节点//可为insert增加一个可变参数版本,这里直接手动链接Node* cur = end()._node;Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;
}

将函数加进自制的list及其组件中。

namespace mystd {template<class T>struct list_node {T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr) {}list_node(T&& x):_data(move(x)), _next(nullptr), _prev(nullptr) {}//支持万能引用的list结点构造函数template<class... Args>list_node(Args&&... args):_data(args...), _next(nullptr), _prev(nullptr) {}};// T T& T*// T cosnt T& const T*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) {}self& operator++() {_node = _node->_next;return *this;}self& operator--() {_node = _node->_prev;return *this;}self operator++(int) {self tmp(*this);_node = _node->_next;return tmp;}self operator--(int) {self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}bool operator!=(const self& s) {return _node != s._node;}bool operator==(const self& s) {return _node == s._node;}};template<class T>class list {public:typedef list_node<T> Node;typedef __list_iterator<T,T&, T*> iterator;typedef __list_iterator<T,const T&, const T*>const_iterator;const_iterator begin() const {return const_iterator(_head->_next);}const_iterator end() const {return const_iterator(_head);}iterator begin() {//return iterator(_head->_next);return _head->_next;}iterator end() {//return iterator(_head->_next);return _head;}void empty_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list() {empty_init();}// lt2(lt1)list(const 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);}// lt3 = lt1list<int>& operator=(list<int> lt) {swap(lt);return *this;}~list() {clear();delete _head;_head = nullptr;}void clear() {iterator it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x) {insert(end(), x);}void push_back(T&& x) {//forward<T>可以保持x的左、右值引用属性insert(end(), forward<T>(x));}template <class... Args>void emplace_back(Args&&... args){Node* newnode = new Node(args...);// 链接节点Node* cur = end()._node;Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;}void push_front(const T& x) {insert(begin(), x);}void pop_front() {erase(begin());}void pop_back() {erase(--end());}iterator insert(iterator pos, const T& x) {Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator insert(iterator pos, T&& x) {Node* cur = pos._node;Node* newnode = newNode(forward<T>(x));Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos) {Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;--_size;return iterator(next);}size_t size() {return _size;}private:Node* _head;size_t _size;};
}

再次测试list

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<list>
#include"mystd.h"
using namespace std;int main() {mystd::list<std::pair<mystd::string,int> >lt;lt.emplace_back("12345",1);cout << endl;lt.push_back(make_pair("6789", 2));//需先构造pair再上传return 0;
}

输出:

string(char* str) 构造
string(string& s)拷贝构造
string(char* str) 构造
string(char* str) 构造string(char* str) 构造
string(string&& s)移动构造

emplace_back对浅拷贝的情况效率会有提升。特别是成员多一些的那种,浅拷贝的类只有构造 +++ 拷贝构造,是直接的资源交换。

但实际上有了右值拷贝后,深拷贝的情况下emplace_back和传统的insertpush_back比起来效率提升不是很明显。使用哪个接口取决于需求。

http://www.xdnf.cn/news/17765.html

相关文章:

  • 如何区分类的关系是关联和聚合?
  • 什么是 Spring MVC?
  • unity shader ——屏幕故障
  • Spring Boot项目通过RestTemplate调用三方接口详细教程
  • 网络协议组成要素
  • 数据结构:链表栈的操作实现( Implementation os Stack using List)
  • 飞算JavaAI 2.0.0深度测评:自然语言编程如何重塑Java开发范式
  • 六、SpringBoot多环境开发
  • MP8128GQ-Z转换器 MPS 电子元器件IC
  • 有限元方法中的数值技术:行列式、求逆、矩阵方程
  • 15_基于深度学习的苹果病害检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • 自己动手造个球平衡机器人
  • NWD-RKA论文阅读
  • C++Linux八股
  • 【完美解决】在 Ubuntu 24.04 上为小米 CyberDog 2 刷机/交叉编译:终极 Docker 环境搭建指南
  • Web前端小游戏轮盘。
  • VisionPro——1.VP与C#联合
  • 派聪明RAG知识库----关于elasticsearch报错,重置密码的解决方案
  • 基于 Easy Rules 的电商订单智能决策系统:构建可扩展的业务规则引擎实践
  • 计算机网络摘星题库800题笔记 第2章 物理层
  • 【Redis在远程控制指令传递中的设计】
  • mysql参数调优之 sync_binlog (二)
  • Unity DOTS(一):ECS 初探:大规模实体管理与高性能
  • Apache Shiro
  • 小白学习pid环控制-实现篇
  • 知名车企门户漏洞或致攻击者远程解锁汽车并窃取数据
  • ENCOPIM, S.L. 参展 AUTO TECH China 2025 广州国际汽车技术展览会
  • SSH浅析
  • 【C#】正则表达式
  • Emscripten 指南:概念与使用