5. 新的类功能
5.1 默认的移动构造和移动赋值
一、背景回顾
C++98/C++03 中,类有 6 个默认成员函数:
构造函数、析构函数、拷贝构造函数、拷贝赋值重载、 取地址重载、const 取地址重载
其中最重要的是前 4 个(构造、析构、拷贝构造、拷贝赋值)。后两个用处不大。
默认成员函数:程序员不写,编译器会自动生成。
二、C++11 新增的两个默认成员函数
三、默认移动构造的生成条件
自动生成的前提条件(缺一不可):
-
用户没有自己实现移动构造函数
-
以下所有函数用户全部没有实现:
-
析构函数
-
拷贝构造函数
-
拷贝赋值重载
-
满足以上条件时,编译器会自动生成一个默认移动构造函数。
默认移动构造的行为:

四、默认移动赋值的生成条件
自动生成的前提条件(缺一不可):
-
用户没有自己实现移动赋值重载函数
-
用户没有实现以下任意一个函数:
-
析构函数
-
拷贝构造函数
-
拷贝赋值重载
-
**默认移动赋值的行为:**与默认移动构造完全类似

五、重要规则:移动与拷贝的互斥关系
如果用户提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
这意味着:
-
自定义了移动语义 → 需要自己考虑拷贝语义的实现
-
反之亦然(提供了拷贝构造/赋值,也可能抑制移动的自动生成,如第三条所述)
规则总结表
5.2成员变量声明时给缺省值
成员变量声明时给缺省值是给初始化列表⽤的,如果没有显⽰在初始化列表初始化,就会在初始化列表⽤这个缺省值初始化,这个我们在类和对象部分讲过了,这里是详细介绍它的链接: C++类与对象详解-3-CSDN博客
5.3defult和delete
控制默认函数:=default 与 =delete
C++11 提供了更精细的方式来控制默认成员函数的生成。
一、=default:显式要求生成默认函数
使用场景:
你想使用某个默认函数,但因为某些原因(如提供了拷贝构造),编译器不会自动生成它。
典型例子:
-
提供了拷贝构造函数 → 编译器不会自动生成移动构造函数
-
可以用
=default显式指定移动构造的生成
效果:
-
告诉编译器:请显式生成该函数的默认版本
-
适用于:移动构造、移动赋值、拷贝构造、拷贝赋值、析构函数、默认构造函数等
二、=delete:禁止生成默认函数
使用场景:
想要限制某些默认函数的生成,禁止对象进行某种操作。
C++98 的旧做法:
-
将函数设为
private -
只声明不定义
-
缺点:代码不够直观,错误信息不友好
C++11 的新做法:
在函数声明后加上
=delete,指示编译器不生成对应函数的默认版本。
语法示例:

术语:
被
=delete修饰的函数称为删除函数(deleted function)
效果:
-
任何尝试调用删除函数的代码都会导致编译错误
-
错误信息比 C++98 的 private 链接错误更清晰
三、对比总结
四、常见应用示例
cpp
class Widget
{
public:
// 默认构造
Widget() = default;
// 禁止拷贝(使类型不可拷贝)
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
// 允许移动
Widget(Widget&&) = default;
Widget& operator=(Widget&&) = default;
// 禁止在堆上分配(删除特定 operator new)
void* operator new(size_t) = delete;
};
int main()
{
Widget w1;
// Widget w2(w1); // 编译错误:拷贝构造被删除
Widget w3(std::move(w1)); // OK:移动构造可用
}
5.4 final与override
这个在继承和多态中讲过,这里附上链接:C++ 继承的奥秘与技巧-CSDN博客C++ 多态详解_c++多态是什么-CSDN博客
6.STL 中的一些变化
C++11 对 STL 进行了多项扩充和改进,主要包括:新容器 、新接口 、遍历方式。
一、新增容器
下图中圈出的即为 STL 新增的容器,其中最有实际价值 的是:

这两个容器前面已经进行了非常详细的讲解。其他新增容器了解即可。有兴趣可以看看我之前的博客,有详细讲解
二、容器的新接口(重要)
C++11 为现有容器增加了许多新接口,最重要的与右值引用和移动语义相关:
三、其他小改动(了解即可)
四、遍历方式
范围 for 循环(Range-based for loop):
cppstd::vector<int> vec = {1, 2, 3, 4, 5}; for (auto& x : vec) { std::cout << x << " "; }在容器部分已经讲过了,这里不再赘述。
总结表:C++11 STL 主要变化

7.lambda
7.1 lambda表达式语法
1. 本质
Lambda 表达式本质上是一个匿名函数对象 。
与普通函数不同的是:它可以定义在函数内部。
2. 类型与接收
Lambda 在语法层面没有显式的类型,一般使用:
-
auto -
模板参数
来接收 Lambda 对象。
3. 语法格式
cpp
[capture-list] (parameters) -> return_type { function_body }
4. 各部分的说明
-
[capture-list]------ 捕捉列表-
必须写在最前面,编译器通过
[]识别 Lambda。 -
用于捕获上下文中的变量,供函数体使用。
-
支持传值 或传引用捕获。
-
即使为空也不能省略
[]。
-
-
(parameters)------ 参数列表-
和普通函数的参数列表相同。
-
不需要参数时可以连同
()一起省略。
-
-
-> return_type------ 返回值类型-
使用追踪返回类型的形式声明。
-
无返回值时可省略。
-
返回值类型明确时,也常省略,由编译器自行推导。
-
-
{ function_body }------ 函数体-
实现具体逻辑。
-
可以使用参数和捕获到的变量。
-
函数体为空也不能省略
{}。
-
cpp
int main()
{
// ⼀个简单的lambda表达式
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl;
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数体不能省略
auto func1 = []//()->int
{
cout << "hello bit" << endl;
return 0;
};
func1();
int a = 0, b = 1;
auto swap1 = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};
swap1(a, b);
cout << a << ":" << b << endl;
return 0;
}
7.2捕捉列表
基本原则:Lambda 表达式默认只能使用自己的函数体内部 以及参数列表 中的变量。
若要使用外层作用域 的变量,必须通过捕获列表 [] 进行捕获。
1、捕获方式分类
- 显式捕获
在捕获列表中逐个写出变量,用逗号分隔,并可指定捕获方式。
-
值捕获 :直接写变量名(如
x) -
引用捕获 :变量名前加
&(如&z)
示例 :[x, y, &z] → x 和 y 值捕获,z 引用捕获
- 隐式捕获
在捕获列表中写一个 = 或 &,让编译器自动推导需要捕获哪些变量。
-
[=]:隐式值捕获 -
[&]:隐式引用捕获
- 混合捕获(隐式 + 显式)
先写隐式捕获符号,再显式写出个别例外变量。
-
[=, &x]:其他变量值捕获,x引用捕获 -
[&, x, y]:其他变量引用捕获,x和y值捕获
混合捕获规则:
-
第一个元素必须是
=或& -
[&, ...]后面只能跟值捕获变量 -
[=, ...]后面只能跟引用捕获变量
2、作用域与捕获限制
函数局部域中的 Lambda
-
只能捕获 Lambda 定义位置之前 的变量
-
不能捕获静态局部变量和全局变量(也不需要捕获,可直接使用)
全局位置的 Lambda
- 捕获列表必须为空 (即
[])
3、mutable 修饰符
默认情况下,值捕获的变量在 Lambda 内部被 const 修饰,不可修改。
-
在参数列表后加
mutable,可以取消常量性 -
效果:允许修改值捕获的变量,但修改的是拷贝的形参,不影响外部原变量
语法要求 :
使用 mutable 时,参数列表 () 不可省略(即使参数为空)。
示例:
cpp
int x = 0;
// 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量
auto func1 = []()
{
x++;
};//但是一般不会这样用
int main()
{
// 只能⽤当前lambda局部域和捕捉的对象和全局对象
int a = 0, b = 1, c = 2, d = 3;
//auto func1 = [a, &b,&a]"a"已经是 lambda 捕获列表的一部分也就是同一个变量不能捕捉多次
auto func1 = [a, &b]
{
// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改
//a++;//值捕获的变量默认被 const 修饰
b++;
int ret = a + b;
return ret;
};
cout << func1() << endl;
// 隐式值捕捉
// ⽤了哪些变量就捕捉哪些变量
auto func2 = [=]
{
int ret = a + b + c;
return ret;
};
cout << func2() << endl;
// 隐式引⽤捕捉
// ⽤了哪些变量就捕捉哪些变量
auto func3 = [&]
{ a++;c++;d++; };
func3();
cout << a << " " << b << " " << c << " " << d << endl;
// 混合捕捉1
auto func4 = [&, a, b]//除了ab值捕捉其他变量都是引用捕捉
{
//a++;b++;
c++;d++;
return a + b + c + d;
};
func4();
cout << a << " " << b << " " << c << " " << d << endl;
// 混合捕捉2
auto func5 = [=, &a, &b]//除了ab引用捕捉其他变量都是值捕捉
{
a++;b++;
//c++;d++;
return a + b + c + d;
};
func5();
cout << a << " " << b << " " << c << " " << d << endl;
// 局部的静态和全局变量不能捕捉,也不需要捕捉
static int m = 0;
//auto func6 = [m,x]无法在 lambda 中捕获带有静态存储持续时间的变量
auto func6 = []
{
int ret = x + m;
return ret;
};
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
// mutable相当于去掉const属性,可以修改了
// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉加了 mutable 后,
// const 属性确实没了,而且相当于用外面变量的值去拷贝一份副本,存在 Lambda 对象内部。
auto func7 = [=]()mutable
{
a++;b++;c++;d++;
return a + b + c + d;
};
cout << func7() << endl;
cout << a << " " << b << " " << c << " " << d << endl;
return 0;
}
7.3lambda的应⽤
- 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
- lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不断接触到
- Lambda 适合短小、局部的函数逻辑;函数很长或需要复用,应写成普通函数。
cpp
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
// ...
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
struct ComparePriceLess
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price < gr._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price > gr._price;
}
};
int main()
{//lamda价值
vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 },
{ "橙⼦", 2.2, 3}, { "菠萝", 1.5, 4 } };
// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
// 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
//匿名函数对象
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price > g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate < g2._evaluate;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate;
});
return 0;
}
7.4lambda的原理
核心结论
Lambda 和范围 for 在底层原理上很像:
-
范围
for→ 底层是迭代器 -
Lambda → 底层是仿函数对象(函数对象)
从汇编指令层面看,不存在 Lambda 这种语法结构,它完全是编译器语法糖。也就是说在我们写了一个lamda之后,编译期会生成一个对应的仿函数的类,类的名称由编译器自己生成
一、编译器如何实现 Lambda
当我们写了一个 Lambda 表达式后,编译器会自动生成一个对应的仿函数类。
类名生成规则
-
类名由编译器按一定规则生成
-
保证不同的 Lambda 生成不同的类名
类成员与函数的映射关系
构造函数与捕获:
-
捕捉列表中的变量 → 仿函数类的成员变量
-
这些变量作为构造函数的实参传递给仿函数类的对象
-
对于隐式捕获 ,编译器会分析 Lambda 体中使用了哪些变量,只传递那些实际用到的变量,通过下面汇编可以很清晰看到

二、汇编层面的印证编译器自动生成一个对应的仿函数类
cpp
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{
}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// lambda rate相当于成员变量
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
auto func1 = [] {
cout << "hello world" << endl;
};
func1();
return 0;
}

这意味着:
-
汇编中会出现仿函数类的构造 、
operator()的调用 -
捕捉的变量会被存入类的成员变量位置
-
不存在原始的 Lambda 语法痕迹
总结表:Lambda 与仿函数类的对应关系
| Lambda 写法 | 编译器生成的内容 |
|---|---|
[x, &y](int a) -> int { return x + y + a; } |
类成员:x(值)、y(引用) operator()(int a) 返回 int |
[=] 隐式值捕获 |
自动识别使用的变量,生成对应成员变量 |
[&] 隐式引用捕获 |
自动识别使用的变量,生成对应引用成员变量 |
8.包装器
8.1function
一、基本概念
std::function 是 C++ 标准库中的一个类模板 ,也是一个通用包装器。
-
定义在
<functional>头文件中 -
可以包装并存储各种可调用对象
二、原型说明
cpp
template <class T>
class function; // 主模板(未定义)
template <class Ret, class... Args>
class function<Ret(Args...)>; // 偏特化版本
-
Ret:返回值类型 -
Args...:参数包(可变参数)
三、可包装的目标类型
std::function 可以存储以下可调用对象:

这些被存储的对象称为 std::function 的目标。
四、空状态与异常
-
空
std::function:不包含任何目标 -
调用空对象的
operator()→ 抛出std::bad_function_call异常
五、核心优势:类型统一
函数指针、仿函数、Lambda 等可调用对象虽然行为相似,但类型各不相同。
std::function 的优势在于:
统一类型,对所有可调用对象进行包装,方便声明、存储和传递。
六、典型应用场景
我们之前的写法vs现代C++11写法
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens)
{
stack<int> st;
for (auto& e : tokens)
{//处理负数与减法的混乱
if (e.size() == 1 &&(e[0] == '+' || e[0] == '-'
|| e[0] == '*' || e[0] == '/'))
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch(e[0])
{
case '+':
st.push(right + left); break;
case '-':
st.push(left - right); break;
case '*':
st.push(right* left); break;
case '/':
st.push(left / right); break;
}
}
else
st.push(stoi(e));
}
return st.top();
}
};
// 使⽤map映射string和function的⽅式实现
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:
int evalRPN(vector<string>& tokens)
{
stack<int> st;
// function作为map的映射可调⽤对象的类型
map<string, function<int(int, int)>> opFuncmap = {
{"+",[](int x,int y) {return x + y; }},
{"-",[](int x,int y) {return x - y; }},
{"*",[](int x,int y) {return x * y; }},
{"/",[](int x,int y) {return x / y; }} };
for (auto& e : tokens)
{
if (opFuncmap.count(e))// 操作符
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
st.push(opFuncmap[e](left,right));
}
else
{
st.push(stoi(e));
}
}
return st.top();
}
};
cpp
int fx(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{}
//静态成员函数没有this指针
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
// 包装各种可调⽤对象
function<int(int, int)> f1 = fx;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = [](int a, int b) {return a + b; };
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
// 包装静态成员函数,静态成员函数没有this指针
// 成员函数要指定类域并且前⾯加&才能获取地址
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
// 包装普通成员函数
// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
//还可以这样写
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
function<double(Plus&, double, double)> f8 = &Plus::plusd;
cout << f8(pd, 1.1, 1.1) << endl;
//cout << f8(Plus(), 1.1, 1.1) << endl;//右值不能绑定到左值
return 0;
}
- 普通函数:函数名在表达式中会隐式转换为函数指针,所以 &func 和 func 等价。
- 成员函数:成员函数名不能隐式转换为成员函数指针。标准规定,成员函数名必须通过 &类名::函数名 才能获得其指针,否则编译错误。
通过 std::function<void()> 统一了所有无参、无返回值的可调用对象类型,实现了运行时多态的可调用对象表。
七、参考资料
8.2bind
一、函数原型
cpp
// 简单版本 (1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// 指定返回类型版本 (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
二、基本概念
-
std::bind是一个函数模板 ,也是一个可调用对象的包装器 -
可以把它看作一个函数适配器(Function Adapter)
-
对接收的可调用对象
fn进行处理后,返回一个新的可调用对象 -
主要用途:调整参数个数 和 调整参数顺序
-
定义在
<functional>头文件中
三、基本用法
一般形式:
cpp
auto newCallable = bind(callable, arg_list);
-
newCallable:返回的新的可调用对象 -
callable:被包装的原始可调用对象 -
arg_list:逗号分隔的参数列表,对应callable的参数
调用过程:
当调用
newCallable时 →newCallable会调用callable→ 并传入arg_list中的参数
四、占位符(Placeholders)
arg_list 中的参数可以包含 占位符 表示newCallable的参数,形如 _n(n 为整数),他表示可调用对象中参数的位置
| 占位符 | 含义 |
|---|---|
_1 |
表示 newCallable 的第一个参数 |
_2 |
表示 newCallable 的第二个参数 |
_3 |
表示 newCallable 的第三个参数 |
| ... | 以此类推 |
作用:
占位符占据了传递给
newCallable的参数位置,用于重新排列参数顺序 或延迟参数绑定。
命名空间:
cpp
// 占位符定义在 placeholders 命名空间中
using namespace std::placeholders;
五、使用示例
cpp
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
// bind 本质返回的⼀个仿函数对象
// 调整参数顺序(不常⽤)
// _1代表第⼀个实参
// _2代表第⼆个实参
// ...
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
// 调整参数个数 (常⽤)
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
// 分别绑死第123个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;
// 成员函数对象进⾏绑死,就不需要每次都传递了
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;
// bind⼀般⽤于,绑死⼀些固定参数
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
// 计算复利的lambda 利率 本金 年份
auto func1 = [](double rate, double money, int year)->double {
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);
cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
cout << func20_3_5(1000000) << endl;
return 0;
}
六、核心功能总结

七、参考资料
9.智能指针
智能指针内容较多,后面我会继续更新它的使用以及原理的深度剖析


