是什么激发了Stroustrup对资源管理技术的偏好顺序?

时间:2015-03-21 06:08:31

标签: c++ c++11 memory-management resources

以下幻灯片来自Bjarne Stroustrups演讲“C ++的本质”:

enter image description here

我是否通过以下简单示例(值,原始指针,资源成员/子对象的智能指针)了解他的技术?:
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)优于智能指针?开销是否非常相似(在生命周期后都破坏资源)?

3 个答案:

答案 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点的顺序。您使用哪种技术更多地取决于用例以及我们实际谈论的资源类型 现在谈谈不同的技术:

  1. 容器:显然,如果您真的想管理多个项目,则应该只使用容器。这主要与C-Style阵列形成对比。所以你不应该做的是:

    Ressource rc[]=new Ressource[10]; 
    

    而是使用例如向量:

    std::vector<Ressource> rc(10);
    

    的优点是复制向量也会复制包含的元素(值语义),并且一旦向量被破坏(例如超出范围),元素就会被自动销毁。这两种机制都不是由C-Style阵列提供的。

  2. RAII:如果必须确保某个时间点必须释放资源,则应将其包装到RAII类中。这可以是存储器(例如,考虑大矩阵),但尤其是与线程,互斥体,文件或套接字等系统资源结合使用。为了清楚起见:在您的示例1中,您“管理”的资源是bar的集合,向量是执行此操作的技术。在示例2中,资源是std::vector<bar>foo是RAII类。现在,如果您的RAII类管理内存(如您的情况),它应该在内部使用智能指针 经验法则:不要使用newdelete - 始终使用std::make_uniquestd::make_shared(一如既往,可能会有特殊情况,您可以使用打破这个规则)。

  3. 这给我们带来了智能指针。基本上它们是经典new / delete组合的替代品。虽然他们擅长自动释放内存,但与完整的RAII类相比,它们有一些缺点:1)它们只适用于内存,但是无法获取锁或文件(但当然可用于保存其他RAII类)。 2)它们不提供值语义(复制share_ptr不复制其内容,并且无法复制unique_ptr。但同样,它们非常适合实现某些类型的RAII类,如果您只想在堆上创建单个对象(免费存储)。

  4. 所以,总结一下:你的例子没有为同一种资源展示不同的技术(容器,RAII,智能指针),但是你通过不同的机制管理不同类型的资源。从您的示例中,数字2严格地比1和3差,因为它手动调用newdelete(需要额外的功能并为错误添加额外的源)。 1或3是否更好取决于您的应用程序。一般来说,我更喜欢1,因为它自动实现值语义,而3则需要手动实现复制/移动赋值运算符/构造函数。但是,如果你绝对需要最小化foo本身的内存(而不是总内存占用量),则3保存2个指针的大小而不是1(忽略由于填充引起的其他可能影响)。