是什么激发了 Stroustrup 对资源管理技术的偏好顺序?
What motivates Stroustrup's order of preference for resource management techniques?
以下幻灯片来自 Bjarne Stroustrups 的演讲 "The Essence of C++":
我是否通过以下简单示例(值、原始指针、资源智能指针 members/subobjects)理解了他的技术?:
1.
class foo{
std::vector<bar> subObj;
int n;
public:
foo(int n) : n(n) {
subObj.push_back(bar("snickers"));
}
};
2.
class foo{
std::vector<bar>* subObj;
int n;
public:
foo(int n) : n(n){
subObj = new std::vector<bar>();
subObj->push_back(bar("snickers"));
}
~foo() {
delete subObj;
}
};
3.
class foo{
std::unique_ptr<std::vector<bar> > subObj;
int n;
public:
foo(int n) : n(n){
subObj(new std::vector<bar>());
subObj->push_back(bar("snickers"));
}
};
为什么 1. 优于 2.?如果我实例化一个 foo 对象并取消引用它以获得 small n 成员的值,那么 vector 成员也将被加载到内存中,对吗?有了2,只有指针被加载到内存中!对于这个问题,我想假设向量在执行过程中会有点大。
此外,为什么 2 (RAII) 优于智能指针?开销是不是很相似(都在生命周期后销毁资源)?
我认为项目符号 2 针对的是其他资源,例如网络连接、文件句柄、与 audio/video 设备的连接等
如果您的 class 实例打开网络连接,您需要确保 class 的析构函数关闭连接。
如果您的 class 实例打开文件或目录,您需要确保 class 的析构函数关闭文件或目录。
如果您的 class 实例打开了与您的音频设备的连接,您需要确保 class 的析构函数关闭连接。
如果您的 class 的实例将程序的显示更改为使用整个屏幕而不是 window,class 的析构函数需要确保显示恢复到 window 模式。
无论 2 是什么,它都不比任何东西更可取。一旦你复制了一个 foo
对象,这是一个等待发生的意外。
Why is 1. preferable over 2.? If I instantiate a foo object and dereference it in order to get the value of the small n member, the vector member will be loaded into memory as well, right? With 2, only the pointer is loaded into memory!
这很令人困惑 - 这里的所有内容都已经在内存中了,那么您究竟要将什么加载到内存中?的确,2 中的 foo
小于 1 中的 foo
,因此您可以在同一缓存行中容纳更多 foo
,当您对foo
的数组而不触及 vector
.
但是 vector
本身只不过是三个指针(用于开始、结束、容量),所以这并不是一个巨大的位置损失。 vector
的内容可以任意大(当然有一个限制)但它们在其他地方。当您实际需要使用 vector
.
时,foo
的较小尺寸带来的微小局部性增益很可能会被额外的间接级别擦除
首先,关于那个presentation/slide的范围:据我所知,Strostrup主要是在谈论函数中的局部变量,而不是成员变量。此外,他想指出的一点是,C++ 中的垃圾收集器(几乎)是不必要的,因为还有其他(可以说是更好的)机制(构建在析构函数之上)。所以你不应该太看重第 1-3 点的顺序。您使用哪种技术更多地取决于用例以及我们实际谈论的资源类型。
现在介绍不同的技术:
容器:显然,如果您真的想管理多个项目,您应该只使用容器。这主要与 C-Style 数组形成对比。所以你不应该做的是:
Ressource rc[]=new Ressource[10];
而是使用例如向量:
std::vector<Ressource> rc(10);
它的优点是复制 vector 也会复制包含的元素(值语义),并且一旦 vector 被销毁(例如超出范围),元素就会自动销毁。 C 样式数组不提供这两种机制。
RAII:如果必须确保某个资源必须在某个时间点释放,则应将其包装到 RAII class 中。这可以是内存(例如考虑一个大矩阵),但在与线程、互斥体、文件或套接字等系统资源结合时尤其知名。明确一点:在您的示例 1 中,您是 "managing" 的资源是 bar
的集合,向量是执行此操作的技术。在示例 2 中,资源是 std::vector<bar>
,foo
是 RAII-class。现在,如果您的 RAII class 管理内存(如您的情况),它应该在内部使用智能指针。
一条经验法则:永远不要使用 new
和 delete
- 始终使用 std::make_unique
或 std::make_shared
(一如既往,可能会有特殊情况,你有打破那个规则)。
这给我们带来了智能指针。基本上它们是 classic new
/ delete
组合的替代品。虽然它们擅长自动释放内存,但与成熟的 RAII class 相比,它们有一些缺点:1)它们仅适用于内存,但例如无法获取锁或文件(但当然可以用来保存其他 RAII classes)。 2) 它们不提供值语义(复制 share_ptr
不会复制其内容,而复制 unique_ptr
是不可能的)。但同样,它们非常适合实现某些类型的 RAII classes,并且如果您只想在堆上创建单个对象(免费存储)。
因此,总结一下:您的示例并未针对同一类型的资源显示不同的技术(容器、RAII、智能指针),而是您通过不同的机制管理不同类型的资源。从您的示例中,数字 2 严格来说比 1 和 3 差,因为它手动调用 new
和 delete
(需要额外的函数并添加额外的错误源)。 1 还是 3 更好取决于您的应用程序。一般来说,我更喜欢 1,因为它自动实现值语义,而 3 需要手动实现 copy/move 赋值运算符/构造函数。但是,如果您绝对需要最小化 foo 本身的内存(而不是总内存占用),那么 3 与 1 相比节省了 2 个指针的大小(忽略由于填充可能产生的其他影响)。
以下幻灯片来自 Bjarne Stroustrups 的演讲 "The Essence of C++":
我是否通过以下简单示例(值、原始指针、资源智能指针 members/subobjects)理解了他的技术?:
1.
class foo{
std::vector<bar> subObj;
int n;
public:
foo(int n) : n(n) {
subObj.push_back(bar("snickers"));
}
};
2.
class foo{
std::vector<bar>* subObj;
int n;
public:
foo(int n) : n(n){
subObj = new std::vector<bar>();
subObj->push_back(bar("snickers"));
}
~foo() {
delete subObj;
}
};
3.
class foo{
std::unique_ptr<std::vector<bar> > subObj;
int n;
public:
foo(int n) : n(n){
subObj(new std::vector<bar>());
subObj->push_back(bar("snickers"));
}
};
为什么 1. 优于 2.?如果我实例化一个 foo 对象并取消引用它以获得 small n 成员的值,那么 vector 成员也将被加载到内存中,对吗?有了2,只有指针被加载到内存中!对于这个问题,我想假设向量在执行过程中会有点大。
此外,为什么 2 (RAII) 优于智能指针?开销是不是很相似(都在生命周期后销毁资源)?
我认为项目符号 2 针对的是其他资源,例如网络连接、文件句柄、与 audio/video 设备的连接等
如果您的 class 实例打开网络连接,您需要确保 class 的析构函数关闭连接。
如果您的 class 实例打开文件或目录,您需要确保 class 的析构函数关闭文件或目录。
如果您的 class 实例打开了与您的音频设备的连接,您需要确保 class 的析构函数关闭连接。
如果您的 class 的实例将程序的显示更改为使用整个屏幕而不是 window,class 的析构函数需要确保显示恢复到 window 模式。
无论 2 是什么,它都不比任何东西更可取。一旦你复制了一个 foo
对象,这是一个等待发生的意外。
Why is 1. preferable over 2.? If I instantiate a foo object and dereference it in order to get the value of the small n member, the vector member will be loaded into memory as well, right? With 2, only the pointer is loaded into memory!
这很令人困惑 - 这里的所有内容都已经在内存中了,那么您究竟要将什么加载到内存中?的确,2 中的 foo
小于 1 中的 foo
,因此您可以在同一缓存行中容纳更多 foo
,当您对foo
的数组而不触及 vector
.
但是 vector
本身只不过是三个指针(用于开始、结束、容量),所以这并不是一个巨大的位置损失。 vector
的内容可以任意大(当然有一个限制)但它们在其他地方。当您实际需要使用 vector
.
foo
的较小尺寸带来的微小局部性增益很可能会被额外的间接级别擦除
首先,关于那个presentation/slide的范围:据我所知,Strostrup主要是在谈论函数中的局部变量,而不是成员变量。此外,他想指出的一点是,C++ 中的垃圾收集器(几乎)是不必要的,因为还有其他(可以说是更好的)机制(构建在析构函数之上)。所以你不应该太看重第 1-3 点的顺序。您使用哪种技术更多地取决于用例以及我们实际谈论的资源类型。
现在介绍不同的技术:
容器:显然,如果您真的想管理多个项目,您应该只使用容器。这主要与 C-Style 数组形成对比。所以你不应该做的是:
Ressource rc[]=new Ressource[10];
而是使用例如向量:
std::vector<Ressource> rc(10);
它的优点是复制 vector 也会复制包含的元素(值语义),并且一旦 vector 被销毁(例如超出范围),元素就会自动销毁。 C 样式数组不提供这两种机制。
RAII:如果必须确保某个资源必须在某个时间点释放,则应将其包装到 RAII class 中。这可以是内存(例如考虑一个大矩阵),但在与线程、互斥体、文件或套接字等系统资源结合时尤其知名。明确一点:在您的示例 1 中,您是 "managing" 的资源是
bar
的集合,向量是执行此操作的技术。在示例 2 中,资源是std::vector<bar>
,foo
是 RAII-class。现在,如果您的 RAII class 管理内存(如您的情况),它应该在内部使用智能指针。
一条经验法则:永远不要使用new
和delete
- 始终使用std::make_unique
或std::make_shared
(一如既往,可能会有特殊情况,你有打破那个规则)。这给我们带来了智能指针。基本上它们是 classic
new
/delete
组合的替代品。虽然它们擅长自动释放内存,但与成熟的 RAII class 相比,它们有一些缺点:1)它们仅适用于内存,但例如无法获取锁或文件(但当然可以用来保存其他 RAII classes)。 2) 它们不提供值语义(复制share_ptr
不会复制其内容,而复制unique_ptr
是不可能的)。但同样,它们非常适合实现某些类型的 RAII classes,并且如果您只想在堆上创建单个对象(免费存储)。
因此,总结一下:您的示例并未针对同一类型的资源显示不同的技术(容器、RAII、智能指针),而是您通过不同的机制管理不同类型的资源。从您的示例中,数字 2 严格来说比 1 和 3 差,因为它手动调用 new
和 delete
(需要额外的函数并添加额外的错误源)。 1 还是 3 更好取决于您的应用程序。一般来说,我更喜欢 1,因为它自动实现值语义,而 3 需要手动实现 copy/move 赋值运算符/构造函数。但是,如果您绝对需要最小化 foo 本身的内存(而不是总内存占用),那么 3 与 1 相比节省了 2 个指针的大小(忽略由于填充可能产生的其他影响)。