ISO/IEC 14882:2011之条款3.8——对象生命周期
3.8 对象生命周期
1、一个对象的生命周期是该对象的运行时属性。如果一个对象是一个类或聚合类型,那么它称为具有非平凡初始化,并且它或其其中一个成员被一个构造器初始化而不是一个平凡默认的构造器。[注:通过一个平凡的拷贝/搬移构造器的初始化是非平凡初始化。 ——注结束]类型T的一个对象的生命周期起始于:
——获得适当对齐的存储以及类型T的大小,以及
——如果对象具有非平凡初始化,那么其初始化是完整的。
类型T的一个对象的生命周期结束于:
——如果T是带有一个非平凡析构器(12.4)的一个类类型,那么该析构器调用开始,或
——该对象所占用的存储被重用或释放。
2、[注:一个数组对象的生命周期在获得适当大小的存储和对齐之后就立即开始,并且其生命周期在数组所占用的存储被重新使用或释放时结束。12.6.2描述了基和成员子对象的生命周期。——注结束]
3、贯穿本国际标准,归属于对象的属性仅在其生命周期期间申请一个给定的对象。[注:特别地,在一个对象的生命周期之前以及在其生命周期的结束之后,对于对象的使用有严格的限制,如以下所描述的,在12.6.2和12.7中。在构造和析构中的一个对象的行为可能与一个对象在其生命周期已经开始并且还没有结束时一样。12.6.2与12.7描述了在构造和析构阶段期间的对象的行为。——注结束]
4、一个程序可以通过重新使用该对象所占用的存储或通过显式地调用具有一个非平凡析构器的一个类类型的一个对象的析构器来结束任一对象的生命周期。对于具有一个非平凡的析构器的一个类类型的一个对象,程序不要求在该对象所占用的存储被重新使用或释放之前显式地调用析构器;然而,如果没有对析构器的显式的调用或者如果没有使用delete-expression(5.3.5)来释放存储,那么析构器将不被隐式地调用,并且任一依赖于析构器所产生的副作用的程序具有未定义行为。
5、在一个对象的生命周期开始之前,但在该对象将要占用的存储已经被分配之后[注:比如,在一个非POD[译者注:朴素陈旧数据]类类型的全局对象的构造之前(12.7)。]或,在一个对象的生命周期已经结束但在该对象所占用的存储被重新使用或释放之前,任一引用该对象将被或已被放置的存储位置的指针可以被使用但仅限于有限的的方式。对于在构造或析构中的一个对象,见12.7。否则,这样的一个指针引用已被分配的存储(3.7.4.2),并且对该指针的使用是良好定义的,就好比该指针是void*类型。这样的一个指针可以被解引用,但结果左值只可以被用在有限的方式下,如下面所描述的。程序具有未定义行为,如果:
——对象将是或过去是具有一个非平凡析构器的类类型并且该指针被用作为一个delete-expression的操作数,
——指针被用于访问访问一个非静态数据成员或调用对象的一个非静态成员函数,或
——指针被隐式地转换为(4.10)一个指向一个基类类型的指针,或
——指针被用作为一个static_cast(5.2.9)的操作数(除了当该转换是到void*,或到void*并然后到char*,或unsigned char*),或
——该指针被用作为一个dynamic_cast的操作数(5.2.7)[例:
struct B { virtual void f(); void mutate(); virtual ~B(); }; struct D1 : B { void f(); }; struct D2 : B { void f(); }; void B::mutate() { new (this) D2; // 重新使用存储——*this的生命周期结束 f(); // 未定义行为 struct B *tmp = this; // OK,this指向有效的存储空间 } void g() { void *p = std::malloc(sizeof(D1) * sizeof(D2)); B* pb = new (p) D1; pb->mutate(); &pb; // OK:pb指向有效的存储空间 void *q = pb; // OK:pb指向有效的存储空间 pb->f(); // 未定义行为,*pb的生命周期已经结束 }
——例结束]
6、类似地,在一个对象的生命周期已经开始之前,但在该对象将要占用的存储已被分配之后,或在一个对象的生命周期已经结束之后并且在该对象所占用的存储在被重新使用或释放之前,任一引用原始对象的泛化左值(glvalue[译者注:3.10])可以被使用,但仅限于有限方式。对于在构造或析构中的一个对象,见12.7。否则,这样的一个左值引用被分配的存储(3.7.4.2),并且对不依赖于其值的泛化左值属性的使用是良好定义的。程序具有未定义行为,如果:
——一个左值到右值的转换(4.1)被应用到这样的一个泛化左值,
——该泛化左值被用于访问一个非静态数据成员或调用该对象的一个非静态成员函数,或
——该泛化左值被隐式地转换为(4.10)指向一个基类类型的引用,或
——该泛化左值被用作为一个static_cast(5.2.9)的操作数,除了当该转换最终转为cv char&或cv unsigned char&,或
——该泛化左值被用作为一个dynamic_cast(5.2.7)的操作数或typeid的操作数。
7、如果,在一个对象的生命周期已经结束之后并且在该对象所占用的存储被重新使用或释放之前,一个新的对象在原始对象所占用的存储位置被创建,那么引用原始对象的一个引用或原始对象的名字将自动引用新的对象,并且,一旦新对象的生命周期已经开始,那么该引用或名字可以被用来操作新的对象,如果:
——新对象的存储完全覆盖原始对象所占用的存储位置,并且
——新对象具有与原始对象相同的类型(忽略顶层的cv限定),并且
——原始对象的类型不被const限定,并且,如果是一个类类型,那么它不包含任一非静态数据成员,其类型被const限定或是一个引用类型,并且
——原始对象是类型T的一个最派生对象,并且新的对象是一个类型T的最派生对象(即,它们不是基类子对象)。[例:
struct C { int i; void f(); const C& operator=(const C&); }; const C& C::operator=(const C& other) { if(this != &other) { this->~C(); // *this的生命周期结束 new (this) C(other); // 类型C的新对象被创建 f(); // 良好定义的 } return *this; } C c1; C c2; c1 = c2; // 良好定义的 c1.f(); // 良好定义的;c1引用类型C的一个新对象
——例结束]
8、如果一个程序结束了具有静态(3.7)、线程(3.7.2)、或自动(3.7.3)存储周期的类型T的一个对象的生命周期,并且如果T具有一个非平凡析构器[注:即,一个对象,一个析构器将为之而被隐式调用——在从带有静态存储周期的一个对象的语句块退出时,从带有线程存储周期的一个对象的线程退出时,或从带有静态存储周期的一个对象的程序退出时。],那么程序必须确保原始类型的一个对象占用了同一个位置,当隐式的析构器调用发生时;否则,程序的行为是未定义的。这是真的,即使该语句块以一个异常退出。[例:
class T { }; struct B { ~B(); }; void h() { B b; new (&b) T; } // 在语句块退出时未定义的行为
——例结束]
9、在一个带有静态、线程或自动存储周期的const对象所占用的存储位置或,在其生命周期结束之前被用来占用的一个const对象的存储位置创建一个新的对象导致未定义行为。[例:
struct B { B(); ~B(); }; const B b; void h() { b.~B(); new (const_cast<B*>(&b)) const B; // 未定义行为 }
——例结束]
10、在这章中,“之前”和“之后”表示“在⋯⋯发生之前”的关系(1.10)。[注:从而,如果在一个线程中正被构造的一个对象从另一个线程被引用而没有充分的同步,那么会导致未定义行为。——注结束]