与托管语言不同,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。
基于堆的生存期
堆对象的生存期内,使用聪明指针。使用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(...) ); }
需要优化性能时,您可能必须使用封装良好拥有指针和删除显式调用。例如,当您实现您自己的低级别的数据结构。
基于堆栈的生存期
在现代的 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(); }"
尽量少用静态的生存期 (全局静态,函数本地静态) 因为可能会出现问题。全局对象的构造函数将引发异常时,会发生什么情况?通常情况下,可能很难调试的应用程序故障。构造顺序是有问题,对于静态生存期的对象,并不是并发性安全。不只是一个问题,对象构造是销毁顺序可以是复杂,尤其是其中所涉及的多态性。即使您的对象或变量不是多态,并不具有复杂构造/销毁订购,则仍然并发线程安全的问题。多线程的应用程序不能安全地修改静态对象中的数据,而无需线程本地存储、 资源锁和其他特殊的预防措施。