目录
1,可变参数模板
1.1,基本语法及原理
1.2,包扩展
4.3,emplace系列接口
2,新的类功能
2.1,默认的移动构造和移动赋值
2.2,default和delete
2.3,final与override
3,STL容器的一些变化
4,lambda
4.1,lambda表达式的语法
4.2,捕捉列表
5,包装器
5.1,function
5.2,bind
1,可变参数模板
1.1,基本语法及原理
- C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板。可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零个或多个模板参数,函数参数包:表示零个或多个函数参数。
template <class...Args>
void Func(Args...args)
{}
template <class...Args>
void Func(Args&...args)
{}
template <class...Args>
void Func(Args&&...args)
{}
- 我们用省略号来指出一个模板参数或函数参数的表示一个包, 在模板参数列表中,class...或 typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...指出 接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板⼀样,每个参数实例化时遵循引用折叠规则。
- 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
1.2,包扩展
对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯一的事情就是扩展它,当扩展⼀个 包时,我们还要提供用于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。
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)
{
//args是N个参数的参数包
//调用showlist,参数包的第一个传给x
//剩下N-1个传给第二个参数
showlist(args...);
}int main()
{
print(1, string("xxxxxx"), 2.2);
return 0;
}
4.3,emplace系列接口
template <class...Args> void emplace_back (Args&&... args);
- C++11以后STL容器新增了empalce系列的接口,empalce系列的接口均为模板可变参数,功能上 兼容push和insert系列,,empalce还支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。
- emplace_back接口比insert和push_back接口更高效。
2,新的类功能
2.1,默认的移动构造和移动赋值
- 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重 载/const 取地址重载,最后重要的是前4个,后两个⽤处不大,默认成员函数就是我们不写编译器 会⽣成⼀个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载,这两个函数在上篇文章中讲过。
- 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 默认移动赋值与上面的移动构造完全类似。
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
class Person
{
public:
Person(const char* name="张三", int age=1)
:_name(name)
,_age(age)
{}
private:
string _name;
int _age;
};int main()
{
Person s1;
Person s2 = s1;
Person s3 = move(s1);return 0;
}
s2调用默认拷贝构造。s3调用移动构造函数,对于string类型,内部实现了移动构造,就嗲用地洞构造。对于int内置类型,逐字节拷贝。
2.2,default和delete
default关键字:
作用:
显式要求编译器生成某个特殊成员函数的默认实现。
适用于:默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符。
使用场景:
当类中定义了其他构造函数时,编译器可能不再自动生成默认构造函数。用default显式恢复:
class MyClass {
public:
MyClass(int x) {} // 自定义构造函数
MyClass() = default; // 显式生成默认构造函数
};
delete关键字
作用
显式禁用某个函数(包括成员函数和普通函数),阻止其被调用。
使用场景:
1,禁止拷贝语义
禁用拷贝构造函数和拷贝赋值运算符,常见于资源管理类(如独占资源):
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 禁用拷贝构造
NonCopyable& operator=(const NonCopyable&) = delete; // 禁用拷贝赋值
};
2,禁用隐式类型转换
删除特定参数类型的重载函数,避免意外的隐式转换:
class MyClass {
public:
void Process(int x) {}
void Process(double) = delete; // 禁止double参数调用
};MyClass obj;
obj.Process(10); // OK
obj.Process(3.14); // 编译错误:函数已删除
3,禁用不希望的函数
例如,禁止通过某些方式构造对象:
class Singleton {
public:
Singleton() = default;
Singleton(const Singleton&) = delete; // 禁止拷贝
};
对比传统的实现方式:
1,default vs 隐式生成
-
显式声明意图更清晰,避免因代码修改导致默认函数被隐式删除。
2,delete vs 私有化函数(C++11前)
-
旧方法:将函数声明为
private
且不实现,但错误在链接阶段才暴露。 -
C++11的
delete
在编译阶段直接报错,提供更清晰的错误信息。
2.3,final与override
final的作用:
-
修饰类:防止类被继承。
class FinalClass final {
// 类的内容
};
-
修饰成员函数:防止成员函数在派生类中被覆盖。
class Base {
public:
virtual void show() final {
std::cout << "Base show" << std::endl;
}
};class Derived : public Base {
public:
void show() override { // 错误:Base::show是final的
std::cout << "Derived show" << std::endl;
}
};
override是一个成员函数的修饰符,用于显式地表示一个成员函数覆盖了基类中的虚函数。它主要用于方法重写。
class Base {
public:
virtual void show() {
std::cout << "Base show" << std::endl;
}
};class Derived : public Base {
public:
void show() override { // 显式声明覆盖
std::cout << "Derived show" << std::endl;
}
};
作用:
-
显式声明覆盖:当派生类中的成员函数覆盖了基类中的虚函数时,使用
override
可以显式地声明这种覆盖关系,增强代码的可读性和安全性。 -
编译器检查:如果使用了
override
,但实际并没有覆盖基类中的虚函数(例如,方法签名不匹配),编译器会报错。
3,STL容器的一些变化
unordered_map和unordered_set的加入。右值引用和移动语义相关的push/insert/emplace系列 接口和移动构造和移动赋值,还有initializer_list版本的构造 等。
容器的范围for遍历
std::vector<int> vec = {1, 2, 3};
for (auto& num : vec) {
std::cout << num << " ";
}
4,lambda
4.1,lambda表达式的语法
- lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接受lambda对象。
- lambda表达式的格式: [capture-list] (parameters)-> return type {function body}
- [capture-list] :捕捉列表,该列表总是出现在lambda 函数的开始位置 ,编译器根据 [] 来判断接下来的代码是否为lambda函数。捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉。捕捉列表为空也不能省略。
- (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以省略()。
- -> return type ::返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- function body::函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
示例:
auto add1 = [](int x, int y)->int {return x + y; };
auto func1 = []
{
cout << "hello world" << endl;
return 0;
};auto swap1 = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};//sort的第三个参数可以传lambda表达式的比较函数
std::sort(vec.begin(), vec.end(), [](int a, int b) {
return a > b; // Sort in descending order
});
4.2,捕捉列表
- lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用 外层作用域中的变量需要进行捕捉。
- 第⼀种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x,y,&z]表示x和y值捕捉,z引用捕捉。
int a = 0, b = 1, c = 2, d = 3;
//显示传值和传引用捕捉
auto func1 = [a, &b]
{
//值捕捉的变量不可以修改
//引用捕捉的变量可以修改
//a++; 报错
b++;
int ret = a + b;
return ret;
};
cout << func1() << endl;//传值捕捉本质是一种拷贝,并且被const修饰了,不能修改
//mutable相当于去掉了const属性,可以修改了
//但是内部修改不会影响外部的值,因为是一种拷贝
auto func = [=]()mutable
{
a++;
b++;
c++;
d++;
return a + b + c + d;
};
cout << func() << endl;
- 第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写一个 = 表示隐式值捕捉,在捕捉列表 写一个 & 表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些变量。
//隐式值捕捉
//用了那些变量就捕捉那些变量
auto func2 = [=]
{
int ret = a + b + c;
return ret;
};
cout << func2() << endl;//隐式引用捕捉
//用了那些变量就捕捉那些变量
auto func3 = [&]
{
a++;
b++;
c++;
};
func3();
cout << a << " " << b << " " << c << " " << endl;
- 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=,&x]表示其他变量隐式值捕捉, x引用捕捉;[&,x,y]表示其他变量引用捕捉,x和y值捕捉。当使用混合捕捉时,第⼀个元素必须是 &或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必须是引用捕捉。
//混合捕捉
auto func4 = [&, a, b]
{
//a++ 报错
//b++ 报错
c++;
d++;return a + b + c + d;
};
func4();
cout << a << " " << b << " " << c << " " << endl;//混合捕捉
auto func5 = [=, &a, &b]
{
a++;
b++;
//c++ 报错
//d++ 报错
};
func5();
- lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态 局部变量和全局变量,静态局部变量和全局变量也不需要捕捉。lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
//局部的静态和全局变量不能捕捉,不需要捕捉就可以用
static int m = 1;
static int n = 2;
auto func6 = [ ]
{
return m + n;
};
5,包装器
5.1,function
- std::function 是⼀个类模板,也是⼀个包装器。std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、 lambda 、bind 表达式等,存储的可调用对象被称为std::function的目标。若std::function不含目标,则成称它为空。调用空std::function的目标导致抛出std::bad_function_call异常。
- 它被定义在<functiinal>头文件中 。
- 函数指针,lambda,仿函数等可调用对象的类型各不相同。std::function的又是就是统一类型,对他们进行包装。
#include <functional>
#include <iostream>int add(int a, int b) {
return a + b;
}struct Functor {
int operator()(int a, int b) {
return a + b;
}
};int main() {
std::function<int(int, int)> f1 = add; // 包装普通函数
std::function<int(int, int)> f2 = Functor(); // 包装仿函数
std::function<int(int, int)> f3 = [](int a, int b) { return a + b; }; // 包装Lambda表达式std::cout << f1(1, 2) << std::endl; // 输出3
std::cout << f2(1, 2) << std::endl; // 输出3
std::cout << f3(1, 2) << std::endl; // 输出3
}
包装成员函数
class Plus
{
public:
Plus(int n=1)
:_n(n)
{}static int plusi(int a, int b)
{
return a + b;
}double plusd(double x, double y)
{
return (x + y) * 10;
}
private:
int _n;
};int main()
{
//包装静态成员函数 需要指明类域+&
function<int(int, int)> f1 = &Plus::plusi;//包装非静态成员函数 需要指明类域+&
//非静态成员函数包含this指针
function<double(Plus*, double, double)> f2 = &Plus::plusd;
Plus pl;
cout << f2(&pl, 1.1, 2.2) << endl;//下面的写法也可以
function<double(Plus, double, double)> f3 = &Plus::plusd;
function<double(Plus&, double, double)> f4 = &Plus::plusd;
return 0;
}
5.2,bind
- bind 是⼀个函数模板, 对接受的可调用的对象 进行处理后返回一个可调用对象。bind 可以用来调整参数个数和参数顺序。bind 也在<function>这个头文件中。
- 调⽤bind的一般形式: auto newCallable = bind(callable,arg_list); 其中 newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的 参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
- arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表示 newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调⽤对象 中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占位符在placeholders的一个命名空间中。
总结:std::bind
是一个绑定器,用于将函数或可调用对象的参数绑定到特定的值上,从而创建一个新的可调用对象。它通常用于固定某些参数,使函数或可调用对象的调用更简单。
调整参数顺序示例:
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int sub(int a, int b)
{
return (a - b) * 10;
}
int main()
{
auto sub1 = bind(sub, _1, _2);
cout << sub1(10, 5) << endl;auto sub2 = bind(sub, _2, _1);
cout << sub2(10, 5) << endl;return 0;
}
调整参数个数示例:
int subx(int a, int b, int c)
{
return (a - b - c) * 10;
}//绑死一些参数,达到修改参数个数的目的
//分别绑死第1,2,3个参数
auto sub3 = bind(subx, 100, _1, _2); //a被绑死=100
cout << sub3(5, 1) << endl;auto sub4 = bind(subx, _1, 100, _2);
cout << sub4(5, 1) << endl;auto sub5 = bind(subx, _1, _2,100);
cout << sub5(5, 1) << endl;
在function部分,在包装一个类的非静态成员函数时,参数必须要传一个对象或者对象的地址,我们可以使用bind来绑定这个参数。
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;class Plus
{
public:
Plus(int n=1)
:_n(n)
{}static int plusi(int a, int b)
{
return a + b;
}double plusd(double x, double y)
{
return (x + y) * 10;
}
private:
int _n;
};int main()
{
//包装静态成员函数 需要指明类域+&
function<int(int, int)> f1 = Plus::plusi;//包装非静态成员函数 需要指明类域+&
//非静态成员函数包含this指针
function<double(Plus*, double, double)> f2 = &Plus::plusd;
Plus pl;
cout << f2(&pl, 1.1, 2.2) << endl;//绑定Plus()参数,传参时就不需要传了
function<double(double, double)> f3 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f3(1.1, 2.2) << endl;
return 0;
}
C++11中的一个重要内容:智能指针,在一篇中更新。