可变参数模板
基本语法及原理
- C++11 支持可变参数模板,即支持可变数量参数的函数模板和类模板。可变数目的参数被作为参数包,存在两种参数包:模板参数包(表示零或多个模板参数);函数参数包(表示零或多个函数参数)。
- 格式:
- 注意模板是
Args...,参数类型是 Args...
template<class ...Args> void Func(Args... args) {}
template<class ...Args> void Func(Args&... args) {}
template<class ...Args> void Func(Args&&... args) {}
- 用省略号来指出一个模板参数或函数参数的表示一个包。在模板参数列表中,
class... 或 typedef... 指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟 ... 指出接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示。每个参数实例化时遵循折叠规则。
- 用
sizeof... 运算符计算参数包中参数的个数(不是 sizeof,这是一个新的运算符)。
template <class ...Args> void Print(Args&&... args)
cout << sizeof...(args) << endl;
}
int main() {
double x = 2.2;
Print();
Print(1);
Print(1, string("xxx"));
Print(1, string("xxx"), x);
return 0;
}
原理说明:
- 编译本质这里会结合引用折叠规则实例化出以下 4 个函数:
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3); (x 是左值,所以 double&)
- 更本质去看有没有可变参数模板,我们是先出这样的多个函数模板才能支持这里的功能。有了可变参数模板,我们进一步被解放,它是类型泛化基础上叠加数量变化,让我们泛型编程更灵活。
包扩展
- 包扩展就是把它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。
- 我们通过在模式的右边放一个省略号(
...)来触发扩展操作。
- 可变参数模板是编译时解析,而
for(size_t i = 0; i < sizeof...(args); i++) 是运行时获取和编译,所以不支持这样使用。
- 参数包展开的格式是
表达式 (参数包)...,... 必须写在整个表达式的末尾,用来触发'对每个参数都执行一次表达式'的展开逻辑。例如:GetArg(args)... 的作用是:把参数包里的每个元素,分别传给 GetArg 函数,然后把所有 GetArg 的返回值,组成一个新的参数列表。
void ShowList() {
cout << endl;
}
template<class T,class ...Args> void ShowList(T x, Args... args) {
cout << x << " ";
ShowList(args...);
}
template<class ...Args> void Print(Args... args) {
ShowList(args...);
}
int main() {
Print();
Print(1);
Print(1, string("xxx"));
Print(1, string("xxx"), 2.2);
return 0;
}
底层细节:
当调用 Print(1, string("xxxxx"), 2.2) 时,编译器将可变参数模板通过模式的包扩展,推导出的重载函数如下:
void ShowList(double z) { cout << z << " "; ShowList(); }
void ShowList(string y, double z) { cout << y << " "; ShowList(z); }
void ShowList(int x, string y, double z) { cout << x << " "; ShowList(y, z); }
void Print(int x, string y, double z) { ShowList(x, y, z); }
template <class T> const T& GetArg(const T& x) {
cout << x << " ";
return x;
}
template <class ...Args> void Arguments(Args... args) { }
template <class ...Args> void Print(Args... args) {
Arguments(GetArg(args)...);
}
int main() {
Print(1, string("xxxxx"), 2.2);
return 0;
}
emplace 接口
- emplace 系列的接口均为模板可变参数,功能上兼容 push 和 insert 系列。
- emplace 还支持直接插入构造 T 对象的参数,可直接在容器空间上构造 T 对象。
- 纯粹的左值插入和右值插入
emplace_back 和 push_back 是一样的;部分场景下,emplace 可以直接构造,push 和 insert 是 构造 + 移动构造 或 构造 + 拷贝构造。
- 综上,emplace 更好用且强大,推荐用 emplace 系列替代 push 和 insert。
int main() {
list<ssp::string> lt;
ssp::string s1("111111");
lt.emplace_back(s1);
cout << "*********************************" << endl;
lt.emplace_back(move(s1));
cout << "*********************************" << endl;
lt.emplace_back("11111");
cout << "*********************************" << endl;
list<pair<ssp::string, int>> lt1;
pair<ssp::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << "*********************************" << endl;
lt1.emplace_back(move(kv));
cout << "*********************************" << endl;
lt(, );
cout << << endl;
;
}
万能引用和完美转发使用
template <class ...Args> ListNode(Args&&... args) : _next(nullptr) , _prev(nullptr) , _data(std::forward<Args>(args)...) {}
template <class ...Args> void emplace_back(Args&&... args) {
insert(end(), std::forward<Args>(args)...);
}
...的所有位置
声明参数包
模板参数列表中:声明类型参数包
- 格式:
template <class... Args> 或 template <typename... Args>
template <class... Args> void Print(Args&&... args);
函数参数列表中:声明值参数包
- 格式:
void Func(T... args)(T 是类型,args 是值参数包)
template <typename... Ts> void Func(Ts... args) {
}
展开参数包
函数调用中展开
- 格式:函数名 (表达式 (参数包)…)
- 关键:
GetArg(args) 是'包含参数包的表达式',... 跟在这个表达式后面,才会对每个参数执行一次 GetArg
template <class... Args> void Print(Args&&... args) {
Arguments(GetArg(args)...);
}
初始化列表 / 数组中展开
template <typename... Ts> void PrintSize(Ts... args) {
int sizes[] = {sizeof...(args)...};
for (auto s : sizes) {
cout << s << " ";
}
}
递归展开
void RecursivePrint() {
cout << endl;
}
template <typename T, typename... Ts> void RecursivePrint(T first, Ts... rest) {
cout << first << " ";
RecursivePrint(rest...);
}
万能引用中 && + …
template <class... Args> void Print(Args&&... args) {
}
新的类功能
默认的移动构造和移动赋值
- 在之前的 C++ 类中,有 6 个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const 取地址重载。
- C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
- 如果没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造函数。
- 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝;自定义类型成员,则需要看这个成员是否实现了移动构造,如果实现了就是调用移动构造,没有实现就调用拷贝构造。
class Person {
public:
Person(const char*, int age = 0) :_name(name) ,_age(age) { }
private:
ssp::string _name;
int _age;
};
int main() {
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
Person s4;
s4 = std::move(s2);
return 0;
}
成员变量声明时给的缺省值
- 成员变量声明时给的缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表用这个值初始化。
- 使用优先级:显示传参 > 有初始化列表的参数列表中的缺省值 > 无初始化列表的声明时给的缺省值。
- 没有初始化列表就用声明时给的缺省值,有的话就用显示传参/参数列表的缺省值。
default 和 delete
default 强制生成某个默认的函数,比如我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成。
- 想要限制某些默认函数的生成,只需在该函数声明加上
=delete 即可,该语法指示编译器不生产对应函数的默认版本,称 =delete 修饰的函数为删除函数。
class Person {
public:
Person(const char*, int age = 0) :_name(name) ,_age(age) { }
Person(Person&& p) = default;
Person(const Person& p) = delete;
private:
ssp::string _name;
int _age;
};
int main() {
Person s1;
Person s3 = std::move(s1);
return 0;
}
final 和 override
- 不想让派生类重写某个虚函数,可以用
final 修饰。
class Car {
public:
virtual void Drive() final {}
};
class Benz :public Car {
public:
virtual void Drive() { cout << "Benz 舒适" << endl; }
};
override 可以帮助用户检测是否正确重写了虚函数 (若是重写正确无影响,不正确会直接报错)。
class Car {
public:
virtual void Drive() {}
};
class Benz :public Car {
public:
virtual void Drive() override { cout << "Benz 舒适" << endl; }
};
STL 中有哪些新变化
- STL 中新添了一些新容器,如
unordered_map,unordered_set,array,forward_list。
- STL 容器中增加了新接口,最重要的就是右值引用和移动语义相关的 push/insert/emplace 系列接口,移动构造和移动赋值,还有
initializer_list 版本的构造。
- 容器的范围 for 遍历:C++11 引入了基于范围的 for 循环(range-based for loop),简化了容器遍历语法,例如
for(auto& elem : container)。