【C++】深入剖析C++11 initializer_list &&新的类功能 &&可变模板参数

发布于:2024-05-11 ⋅ 阅读:(28) ⋅ 点赞:(0)

目录

一、std::initializer_list

1、std::initializer_list是什么类型 

2、std::initializer_list 的应用场景

①给自定义容器赋值

② 传递同类型的数据集合

二、新的类功能

1、默认成员函数

2、关键字default

3、关键字delete

三、可变参数模板


一、std::initializer_list

  • initializer_list是一种标准库类型,用于表示某种特定类型的值的数组。和vector一样,initializer_list也是一种模板类型,定义 initializer_list 对象时,必须说明列表中所含元素的类型。和vector不一样的是,initializer_list 对象中的元素永远是常量值,我们无法改变 initializer_list 对象中元素的值。
  •  initializer_list 可以作用于可变数量的实参,有时我们无法提前预知应该向函数传递几个实参。为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:①如果所有的实参类型相同,可以传递一个名为 initializer_list 的标准库类型;②如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板。
  •  作用于 initializer_list 对象的begin和end操作类似于vector对应的成员。begin()成员提供一个指向列表首元素的指针,end()成员提供一个指向列表尾元素的指针。
  •  含有 initializer_list 形参的函数也可以同时拥有其他形参。
  •  类模板 initializer_list 用于访问初始化列表(initialization list),列表元素的数据类型为const T.编译器从花括号(brace)封闭的、元素由逗号分隔开的初始化列表自动构造initializer_list模板类,例如:
  • auto il = {10,20, 30}; // the type of il is an initializer_list<int>
  • initializer_list 官方文档

1、std::initializer_list是什么类型 

 2、std::initializer_list 的应用场景

这里 arr 没有显式指定长度,因此,它的初始化列表可以是任意长度。

同样, std::map、 std::set、 std::vector 也可以在初始化时任意书写需要初始化的内容。

自定义的 Foo 却不具备这种能力,只能按部就班地按照构造函数指定的参数列表进行赋值。

实际上, stl 中的容器是通过使用 std::initializer_list 这个轻量级的类模板来完成上述功能支持的。我们只需要为 Foo 添加一个 std::initializer_list 构造函数,它也将拥有这种任意长度初始化的能力

①给自定义容器赋值

这里定义了两个自定义容器,一个是 FooVector,采用 std::vector<int> 作为内部存储;另一个是 FooMap,采用 std::map<int, int> 作为内部存储。

可以看到, FooVector、 FooMap 的初始化过程,就和它们使用的内部存储结构一样。

这两个自定义容器的构造函数中, std::initializer_list 负责接收初始化列表。并通过我们熟知的 for 循环过程,把列表中的每个元素取出来,并放入内部的存储空间中。

② 传递同类型的数据集合

如上述所示,在任何需要的时候, std::initializer_list 都可以当作参数来一次性传递同类型的多个数据。 

二、新的类功能

1、默认成员函数

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

  •  构造函数
  • 析构函数
  • 拷贝构造函数
  • 拷贝赋值重载
  • 取地址重载
  • const 取地址重载

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

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

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

 2、关键字default

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

3、关键字delete

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

三、可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比 C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改 进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现 阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止,以后大 家如果有需要,再可以深入学习。

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数 包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的, 只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特 点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变 参数,所以我们的用一些奇招来一一获取参数包的值。

 

在不定参数的模板函数中,还可以通过如下方式获得args的参数个数:

【逗号表达式展开参数包】

  • 这种展开参数包的方式,不需要通过递归终止函数,是直接在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数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在 数组构造的过程展开参数包.


网站公告

今日签到

点亮在社区的每一天
去签到