C++标准下storage reuse(存储复用)是否必须销毁原有对象?
背景:C++标准中对象生命周期相关规定
根据[basic.life/1]条款(加粗为标注内容):
类型T对象的*lifetime(生命周期)*始于满足以下全部条件时:
- 已获得大小、对齐符合类型T要求的storage(存储),且
- 其initialization(初始化)(如有)已完成(包含空初始化,见[dcl.init])。例外情况:若该对象是联合体成员或其子对象,则其生命周期仅在该联合体成员是联合体的初始化成员时(见[dcl.init.aggr]、[class.base.init]),或符合[class.union]、[class.copy.ctor]中描述的场景,或符合[allocator.members]中描述的场景时才会开启。
类型T对象o的*lifetime(生命周期)*结束于满足以下任一条件时:
- 若T是非类类型,该对象被销毁;或
- 若T是类类型,destructor(析构函数)调用启动;或
- 该对象占用的storage(存储)被释放,或被不属于o嵌套子对象的其他对象复用(见[intro.object])。
各阶段时序如下:
|------------|----|--------------|----------|-------------|----|--------------| <----------> <------------> <--------> <-----------> <------------> storage object object object storage allocation construction lifetime destruction deallocation <-----------------------------------------------> storage duration
根据[basic.life/8]条款(加粗为标注内容):
若某对象生命周期结束后,其占用的存储被复用或释放前,在原对象占据的存储位置创建了新对象,则当原对象可被新对象透明替换(见下文)时,指向原对象的指针、引用原对象的引用、原对象的名称会自动指向新对象,且在新对象生命周期开启后可用于操作新对象。
问题解答
1. placement new运算符是不是实现storage reuse的唯一方式?
不是。除了常用的显式调用placement new的方式外,标准还规定了另外几种合法的存储复用实现:
- 联合体的活跃成员切换:按照[class.union]的要求,给非初始活跃的联合体成员赋值时,标准会隐式在该成员对应的存储位置创建新对象,复用联合体占有的存储,完全不需要手动调用placement new
- 分配器的construct操作:不管是标准库的
std::allocator,还是符合分配器约定的自定义分配器,其construct成员函数本质就是在已申请好的存储上构造对象,也属于合法的存储复用 - C++20新增的隐式生命周期类型自动创建规则:对于满足隐式生命周期要求的类型(比如标量类型、所有平凡类型等),只要拿到了对齐、大小符合要求的存储,操作存储的过程中标准允许自动隐式创建对应类型的对象,不需要显式调用构造函数或者placement new就能完成存储复用
2. [basic.life/8]中规定的效力,是否真的要求原对象已被销毁(即满足「对象生命周期已结束」的前提)?
是,这是[basic.life/8]生效的硬性前置条件。条款原文开篇就明确限定了适用场景是「原对象生命周期结束后,存储被复用或释放前」,如果原对象生命周期还没结束,就直接往它占的存储上建新对象,属于未定义行为,根本不满足透明替换的要求。
这里要额外提一句:原对象生命周期结束不等于必须手动调用它的析构函数。比如非类类型的对象销毁本来就不需要显式调用析构,另外按照[basic.life/1]的生命周期结束规则,一旦原对象的存储被其他对象复用,原对象的生命周期会自动终止,这种场景下只要后续新对象满足透明替换的其他要求,还是可以适用[basic.life/8]的规则的。
内容的提问来源于stack exchange,提问作者Géry Ogam
相关产品推荐
相关产品推荐

