对象生存期和资源管理(现代C++)

与托管语言不同,C++ 不具有垃圾回收 (GC),程序运行时,会自动释放否更长的时间-使用内存资源。 在 C++ 对象生存期直接与资源管理。 本文档介绍在 C++ 对象生存期以及如何管理它影响的因素。

C + + 不具有 GC,主要是因为它不会处理非内存资源。 只有确定性析构函数类似于 C++ 中可以均匀地处理内存和非内存资源。 GC 还有其他问题,如内存和 CPU 消耗,本地性更高的开销。 但 universality 不能减轻通过聪明的优化的基本问题。

概念

在对象生存期管理重要的事情是封装 — 知道资源,该对象的所有者,或如何删除它们,或甚至是否它所拥有的任何资源根本没有人员使用的对象。 它只是必须销毁该对象。 核心 C++ 语言旨在确保该对象被销毁以正确的时间,即为块会退出,按相反顺序构造。 当对象已被破坏时,其基和成员被特定的顺序。 语言自动销毁的对象,除非您执行特殊操作 (如堆分配或新的位置。 例如, 聪明指针 (如unique_ptr和shared_ptr,和标准模板库 (STL) 容器 (如vector,封装new/delete和new[]/delete[]中的对象,它具有析构函数。 这就是为什么它是使用智能指针和 STL 容器如此重要。

生命周期管理中的另一个重要概念: 析构函数。 析构函数封装资源释放。 (通常使用助记符是 RRID,资源释放被销毁。从"系统"中获得,和具有更高版本提供的一些资源。 内存是最常见的资源,但也有文件、 套接字、 纹理和其他非内存资源。" 拥有"资源意味着当您需要它,但您还必须与它完成后将其释放,您可以使用它。 当对象已被破坏时,其析构函数释放它所拥有的资源。

最后一个概念是 DAG (定向非循环图形)。 在程序中的所有权结构形成 DAG。 没有任何对象都可以拥有其自身 — — 的不仅是不可能,但还没有本质意义。 但两个对象可以共享的第三个对象的所有权。 几种类型的链接都可能像下面这样的 DAG 中: A 是 B 的成员 (B 拥有 A),C 存储vector<D> (C 拥有每个 D 的元素),E 存储shared_ptr<F> (E F 的所有权可能与共享其他对象),等等。 只要不有任何循环,并且在 DAG 中的每个链接由某个对象代表具有的析构函数 (而不是原始指针、 句柄或其他机制),则资源泄漏是不可能的因为语言可以防止它们。 不再需要,而无需运行垃圾回收器后,立即释放资源。 跟踪的生存期是范围堆栈、 基、 成员和相关的情况下,对于无开销和成本较低的shared_ptr。

Hh438473.collapse_all(zh-cn,VS.110).gif基于堆的生存期

堆对象的生存期内,使用聪明指针。 使用shared_ptr和make_shared的默认指针和分配器。 使用weak_ptr中断周期、 执行缓存,并观察的对象,而不会影响或假定任何有关其生存期。

void func() {

auto p = make_shared<widget>(); // no leak, and exception safe
...
p->draw(); 

} // no delete required, out-of-scope triggers smart pointer destructor

使用unique_ptr的唯一的所有权,例如,在 pimpl 用法。 (参见 编译时封装(现代C++) Pimpl。)使unique_ptr的所有显式的主要目标new的表达式。

unique_ptr<widget> p(new widget());

您可以使用原始指针为非所有权和观察。 Dangle 可能不具有所有权的指针,但它不能泄漏。

class node {
  ...
  vector<unique_ptr<node>> children; // node owns children
  node* parent; // node observes parent, which is not a concern
  ...
};
node::node() : parent(...) { children.emplace_back(new node(...) ); }

需要优化性能时,您可能必须使用封装良好拥有指针和删除显式调用。 例如,当您实现您自己的低级别的数据结构。

Hh438473.collapse_all(zh-cn,VS.110).gif基于堆栈的生存期

在现代的 C++ 中, 基于堆栈的作用域 是强有力的方式编写可靠的代码,因为它将自动合并 堆栈生存期 和 数据成员的生存期以高效率--跟踪的生存期是本质上是可用的系统开销。 堆对象生存期需要勤快手动管理,原因可能是资源泄漏和低效率,尤其是当您正在使用原始指针。 此代码,它演示了基于堆栈的作用域,请考虑:

class widget {
private:
  gadget g;   // lifetime automatically tied to enclosing object
public:
  void draw();
};

void functionUsingWidget () {
  widget w;   // lifetime automatically tied to enclosing scope
              // constructs w, including the w.g gadget member
  …
  w.draw();
  …
} // automatic destruction and deallocation for w and w.g
  // automatic exception safety, 
  // as if "finally { w.dispose(); w.g.dispose(); }"

尽量少用静态的生存期 (全局静态,函数本地静态) 因为可能会出现问题。 全局对象的构造函数将引发异常时,会发生什么情况? 通常情况下,可能很难调试的应用程序故障。 构造顺序是有问题,对于静态生存期的对象,并不是并发性安全。 不只是一个问题,对象构造是销毁顺序可以是复杂,尤其是其中所涉及的多态性。 即使您的对象或变量不是多态,并不具有复杂构造/销毁订购,则仍然并发线程安全的问题。 多线程的应用程序不能安全地修改静态对象中的数据,而无需线程本地存储、 资源锁和其他特殊的预防措施。

请参见

其他资源

返回C++ (现代C++)的欢迎

C++语言参考

标准C++库参考