以下幻灯片来自Bjarne Stroustrups演讲“C ++的本质”:
我是否通过以下简单示例(值,原始指针,资源成员/子对象的智能指针)了解他的技术?:
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对象并取消引用它以获得小 n 成员的值,那么vector成员也会被加载到内存中,对吧?使用2,只有指针被加载到内存中!对于这个问题,我想假设在执行期间向量会有点大。
另外,为什么2(RAII)优于智能指针?开销是否非常相似(在生命周期后都破坏资源)?
答案 0 :(得分:3)
我相信子弹2的目标是其他资源,例如网络连接,文件句柄,音频/视频设备连接等。
如果您的类的实例打开网络连接,您需要确保该类的析构函数关闭连接。
如果您的类的实例打开文件或目录,则需要确保该类的析构函数关闭文件或目录。
如果班级的实例打开了与音频设备的连接,则需要确保该类的析构函数关闭连接。
如果您的类的实例更改了程序的显示以使用整个屏幕而不是窗口,则类的析构函数需要确保显示恢复到窗口模式。
答案 1 :(得分:3)
无论2是什么,它都不比任何东西更可取。只要您复制foo
对象,就会发生意外事故。
为什么1.优先于2.?如果我实例化一个foo对象并取消引用它以获取小n成员的值,那么vector成员也将被加载到内存中,对吧?使用2,只有指针被加载到内存中!
这很混乱 - 这里的所有内容已经在内存中了,你究竟要加载到内存中了什么?确实,2中的foo
小于1中的foo
,因此您可以在同一个缓存行中放入更多foo
,从而在运行时可能会产生更好的性能foo
的数组,但未触及vector
。
但是vector
本身只是三个指针(开始,结束,容量),所以它不像是一个巨大的局部性损失。 vector
的内容可以任意大(当然可达到极限),但它们在其他地方。从foo
的较小尺寸获得的微小局部性增益很可能在您实际需要使用vector
时被额外的间接级别擦除。
答案 2 :(得分:1)
首先,关于演示文稿/幻灯片的范围:据我所知,Strostrup主要讨论函数中的局部变量,而不是成员变量。此外,他想要提出的一点是,c ++中的垃圾收集器(几乎)是不必要的,因为还有其他 - 可以说是更好的 - 机制(构建在析构函数之上)。所以你不应该过分重视1-3点的顺序。您使用哪种技术更多地取决于用例以及我们实际谈论的资源类型 现在谈谈不同的技术:
容器:显然,如果您真的想管理多个项目,则应该只使用容器。这主要与C-Style阵列形成对比。所以你不应该做的是:
Ressource rc[]=new Ressource[10];
而是使用例如向量:
std::vector<Ressource> rc(10);
的优点是复制向量也会复制包含的元素(值语义),并且一旦向量被破坏(例如超出范围),元素就会被自动销毁。这两种机制都不是由C-Style阵列提供的。
RAII:如果必须确保某个时间点必须释放资源,则应将其包装到RAII类中。这可以是存储器(例如,考虑大矩阵),但尤其是与线程,互斥体,文件或套接字等系统资源结合使用。为了清楚起见:在您的示例1中,您“管理”的资源是bar
的集合,向量是执行此操作的技术。在示例2中,资源是std::vector<bar>
,foo
是RAII类。现在,如果您的RAII类管理内存(如您的情况),它应该在内部使用智能指针
经验法则:不要使用new
和delete
- 始终使用std::make_unique
或std::make_shared
(一如既往,可能会有特殊情况,您可以使用打破这个规则)。
这给我们带来了智能指针。基本上它们是经典new
/ delete
组合的替代品。虽然他们擅长自动释放内存,但与完整的RAII类相比,它们有一些缺点:1)它们只适用于内存,但是无法获取锁或文件(但当然可用于保存其他RAII类)。 2)它们不提供值语义(复制share_ptr
不复制其内容,并且无法复制unique_ptr
。但同样,它们非常适合实现某些类型的RAII类,如果您只想在堆上创建单个对象(免费存储)。
所以,总结一下:你的例子没有为同一种资源展示不同的技术(容器,RAII,智能指针),但是你通过不同的机制管理不同类型的资源。从您的示例中,数字2严格地比1和3差,因为它手动调用new
和delete
(需要额外的功能并为错误添加额外的源)。 1或3是否更好取决于您的应用程序。一般来说,我更喜欢1,因为它自动实现值语义,而3则需要手动实现复制/移动赋值运算符/构造函数。但是,如果你绝对需要最小化foo本身的内存(而不是总内存占用量),则3保存2个指针的大小而不是1(忽略由于填充引起的其他可能影响)。