C++11 可变参数模板,emplace,decltype
一.可变参数模板
可变参数模板是C++11新增的特性之一,让我们能够创建可以接收可变参数的函数模板和类模板
1.介绍与使用
2.获取参数包中的每个参数
1.错误示范
这里的语法并不支持arg[i]的方式来获取参数包中的每个参数
只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点
以下是错误示范:
template <class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
for (int i = 0; i < sizeof...(args); i++)
{
cout << args[i] << " ";
}
cout << endl;
}
为什么会这样呢?
那么我们就是想看,该怎么办呢?
2.利用编译时递归来查看参数包
既然你在编译期间就会对这个参数包进行解析,那么我就只能在编译期间来看了呗
既然要在编译期间就看,那么就必须借助一个你编译器在编译期间就要做的事情
此刻有位大佬想到了利用模板的实例化,用魔法来打败魔法
你不是要在编译期间就去实例化吗?那么我拆分一下,再写一个模板
把你这个参数个数为n的参数包拆分成1个参数+长度为n-1的参数包
(n=1+n-1)
每次查看那一个参数,不就行了吗?
而这种方法不就能够拆分成一个一个的子问题了吗?
因此不就能够利用递归了吗?
因此这位大佬设计出了编译时递归的方法
//递归出口
void _ShowList()
{
cout << endl;
}
template <class T, class ...Args >
void _ShowList(T val, Args... args)
{
cout << val << " ";
_ShowList(args...);
}
template <class ...Args>
void ShowList(Args... args)
{
_ShowList(args...);
}
int main()
{
ShowList();
ShowList(1);
ShowList(1,2);
ShowList(1,2.2,4.4,"hello world");
return 0;
}
查看成功,下面我们来分析一下过程
但是你为什么这样定义顺序呢?
好怪啊,整个递归都是往上走的
我也想让它往下走啊,可是编译器编译时只会向上找,所以要把递归子函数和递归出口写在可变参数模板的上面
3.利用编译时数组的初始化
其实还有1种方法:利用编译时数组的初始化来搞事情
int a[]={1,2,3,4};
对于这种定义数组的方式,编译器会在编译期间解析数组的内容
从而才能确定这个数组的大小
template <class T>
void PrintArgs(T t)
{
cout << t << " ";
}
template <class ...Args>
void ShowList(Args... args)
{
int a[] = { (PrintArgs(args),0)...,0 };//因为不能分配大小为0的数组,因此最后的时候加个0
cout << endl;
}
刚才的可变参数模板中参数的获取在实际当中并不常用,因此大家了解即可,介绍那个主要是为了让我们更好地去理解可变参数模板这个东西
让我们知道它其实也就只是让编译器做了更多的事情而已,没什么神奇之处
可变参数模板在emplace系列的插入接口当中具有非常重要的作用和价值
二.emplace系列接口的使用
1.接口声明
我们就以list为例,先看一下STL库中emplace的接口声明
2.emplace与insert的对于深拷贝类的区别
我们拿出我们的string类来C++: string的模拟实现里面有我们模拟实现的string类的源代码,代码就不放过来了,太长了,大家自取
使用自己实现的是为了让我们更好地观察对象的拷贝情况)(你插入一个元素到底使用的是拷贝构造还是移动构造)
而list我们就先用一下STL库里面的,因为我们自主实现的还没有加上emplace系列接口
双方的代表:
emplace系列:emplace_back
insert系列: push_back
大家不要急,下面就给大家看他们的区别
我们知道,移动构造就是转移资源,它的消耗跟浅拷贝是一样的,
因此对于深拷贝的类来说,多一次移动构造,少一次移动构造的差别并不大
所以emplace系列的接口对于深拷贝的类来说,提升并不明显
那么浅拷贝的类呢?
有很大的提升
在介绍对于浅拷贝的类的优化之前,我们再来写一份代码,说明一下insert系列接口和emplace系列接口使用上的区别和建议
综上,我们建议使用emplace的时候直接传入用于构造该对象的参数即可
此时emplace会通过不断的传递可变参数包,一直传递到构造该对象的时候对该对象进行直接构造
3.emplace与insert的对于浅拷贝类的区别
4.小小总结
下面我们完善一下我们之前的vector和list的emplace相关的接口
5.vector实现emplace系列接口
至于三个点…都是出现在什么地方,大家都试一试吧…这个真不知道有啥规律
代码:
template<class ...Args>
iterator emplace(iterator pos, Args&&... args)
{
assert(pos >= _start && pos <= _finish);
int gap = pos - _start;
if (_finish == _endOfStorage)
{
int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
pos = _start + gap;
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);
--end;
}
*pos = T(forward<Args>(args)...);//重点
++_finish;
return pos;
}
template<class ...Args>
void emplace_back(Args&&... args)
{
emplace(end(), forward<Args>(args)...);
}
6.list实现emplace系列接口
代码:
template<class ...Args>
iterator emplace(iterator pos, Args ...args)
{
Node* newnode = new Node(forward<Args>(args)...);
Node* next = pos._node;
Node* prev = next->_prev;
newnode->_prev = prev;
newnode->_next = next;
prev->_next = newnode;
next->_prev = newnode;
++_size;
return iterator(newnode);
}
template<class ...Args>
void emplace_back(Args ...args)
{
emplace(end(),forward<Args>(args)...);
}
template<class ...Args>
void emplace_front(Args ...args)
{
emplace(begin(), forward<Args>(args)...);
}
三.default,delete
C++98中
//不想让A类对象被拷贝
class A
{
public:
A()
{}
private:
//声明为私有,且只声明不实现,就算通过友元等等方法能够使用的话,也会爆链接错误
A(const A& aa);
int _a = 100;
};
C++11中
//不想让A类对象被拷贝
class A
{
public:
A()
{}
A(const A&& aa) = delete;
private:
int _a = 100;
};
四.final,override
1.final修饰父类,让父类不能被继承
关键字final:用来修饰父类,让父类不能被继承
C++11之前的做法:
父类构造函数只声明不实现,且私有化
class A
{
private:
A() {}
};
class B :public A
{};
int main()
{
B b;
return 0;
}
2.final修饰虚函数,表示该虚函数不能再被重写
其实这个final也可以用来修饰虚函数
final修饰虚函数,表示该虚函数不能再被重写
override:检查子类虚函数是否完成了对父类虚函数的重写
如果没有重写,那么编译时就会报错
五.decltype
decltype是根据表达式的实际类型推演出定义变量时所用的类型
template<class T1, class T2>
void C(T1 t1, T2 t2)
{
decltype(t1 * t2) ret = t1 * t2;
cout << typeid(ret).name() << endl;
cout << ret << endl;
}
int main()
{
const int a = 1;
double b = 12.25;
decltype(a * b) c; // c的类型是double
decltype(&a) p; //p的类型是int*
cout << typeid(c).name() << endl;
cout << typeid(p).name() << endl;
C(1.1, 10);//double和int运算后转为double
return 0;
}
注意:
1.typeid(变量名).name()只能用来获取一个变量的类型,但是无法通过获取到的类型去定义变量
2.而decltype除了能够推演表达式的类型,还能推演函数返回值的类型
六.initializer_list
initializer_list是C++11新增的一个容器,就是我们常见的{}
只提供了这么几个函数,它的作用是用来对容器进行初始化的,C++11之后STL库当中的很多容器都支持了initializer_list来进行初始化
因此我们才可以这样来构造容器的对象 :
而我们自己的vector和list是不支持的
那么下面我们就实现一下吧
vector:
list:
vector(const initializer_list<T>& il)
:_start(nullptr)
,_finish(nullptr)
,_endOfStorage(nullptr)
{
for (auto& e : il)//直接范围for+push_back即可
{
push_back(e);
}
}
vector<T>& operator=(const initializer_list<T>& il)
{
vector<T> tmp(il);//直接现代写法
swap(tmp);
return *this;
}
list(const initializer_list<T>& il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}
list<T>& operator=(const initializer_list<T>& il)
{
list<T> tmp(il);//直接现代写法
swap(tmp);
return *this;
}
七.列表初始化
列表初始化也是{},C++98当中,{}只允许对数组进行初始化,C++11之后支持一切皆可用列表初始化进行初始化
最好的作用有两个:
(1)方便进行初始化了
(2)new的时候可以指定初始值了
以上就是C++11 可变参数模板,emplace,decltype的全部内容,希望能对大家有所帮助!!!