【C/C++】从内存角度透视现代C++的(部分)关键特性
作者:wallace-lai 发布:2024-03-13 更新:2024-03-14
一、理解对象的移动
考虑下面的一个常见对象Widget:
struct Point {
int x;
int y;
};
struct WidgetBase {
double value;
};
struct Widget : WidgetBase {
int no;
std::string text;
Point *data;
};
它的深拷贝是如何实现的?
类Widget的内存布局示意图(忽略string中的指针)如下:

如下所示,要实现对w2的完整深拷贝:
Widget w1, w2;
w1 = w2;
需要做以下几件事:
(1)拷贝WidgetBase基类(委托给WidgetBase的拷贝构造函数);
(2)拷贝基本类型int no;
(3)拷贝对象类型string text(调用string的拷贝构造函数);
(4)深拷贝指针类型Point *data(创建堆内存指针,调用构造函数);
它的移动是如何实现的?
理解了深拷贝的过程,那么移动的过程就很好理解了,内存布局示意图如下所示:

如下所示,要实现对w2的移动,
Widget w1, w2;
w1 = std::move(w2);
需要这么做:
(1)对WidgetBase基类执行std::move;
(2)拷贝基本类型int no(基本类型的移动即拷贝);
(3)移动对象类型string text(调用std::move函数);
(4)移动指针类型Point *data(将w2的当前指针设置为nullptr,将w1的当前指针指向原先的内存,这是唯一需要自己移动的成员);
二、集合变更会有深拷贝代价
各种集合的push_back、insert、emplace、resize、erase等变更操作可能导致容器扩容。如果元素类型不支持move,那么扩容会触发容器上现存元素的拷贝构造(深拷贝)。
问题:自定义类型分别支持和不支持深拷贝,观察容器扩容时的性能差异?
如果元素类型支持move,那么扩容会更加容易,只需要:
(1)容器上现存所有元素中的内联对象会被拷贝;
(2)容器上现存所有元素中的分离内存会被“移动”;
问题:编译器什么情况下会,什么情况下不会为对象类型生成移动构造函数?
三、智能指针
带删除状态的unique_ptr

四、lambda表达式
lambda捕获值
有以下lambda表达式:
Point p1 {100, 200};
Point p2 {100, 200};
auto lam = [=] (int n)
{
p1.print(n);
p2.print(n);
}
其对应的内存布局示意如下:
struct Lambda {
Point p1;
Point p2;
Lambda(const Point &p1, const Point &p2)
: p1(p1), p2(p2) {}
void operator()(int n) {
p1.print(n);
p2.print(n);
}
};
即:捕获的两个值存入了Lambda结构中的成员变量中。
lambda捕获引用
有以下lambda表达式:
Point p1 {100, 200};
Point p2 {100, 200};
auto lam = [&] (int n)
{
p1.x += n;
p2.x += n;
}
其对应的内存布局示意如下:
struct Lambda {
Point &p1;
Point &p2;
Lambda(const Point &p1, const Point &p2)
: p1(p1), p2(p2) {}
void operator()(int n) {
p1.x += n;
p2.x += n;
}
};
即:Lambda的成员变量类型变成了引用类型。
