C++中多次透明替换对象后复用原指针是否符合标准?
根据C++标准,对象可被透明替换。但标准是否允许对象被多次替换后仍复用原指针?
具体而言,如下示例中第二次调用new (this) C(other)是否仍符合标准中的如下条款:
若某对象生存期结束后,且在其占用的存储被复用或释放前,在该存储位置创建新对象,则指向原对象的指针、引用或原对象的名称将自动指代新对象,且新对象生存期开始后可用于操作新对象——前提是原对象可被新对象透明替换。
示例代码
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对象(存储已被复用?) new (this) C(other); // 再次创建新的C对象(存储再次被复用?)! f(); // 这里是否仍定义良好? } return *this; } C c1; C c2; c1 = c2; // 这里是否仍定义良好?
核心疑问
从技术层面看,第二次调用new (this) C(other)时,新对象被创建,存储位置被复用,随后原(前一个)对象的生存期才结束?这是否不符合标准条款,导致上述代码出现未定义行为(UB)?因为标准明确要求存储复用前原对象生存期已结束,而此处似乎是先复用存储再结束生存期?
结论:代码存在未定义行为
关键分析点
第一次替换的合法性:
第一次调用this->~C()结束了原对象的生存期,随后new (this) C(other)在同一块存储上创建新对象,完全符合标准条款的要求——此时this指针自动指代新创建的对象,这一步是合法的。第二次替换的问题:
第二次调用new (this) C(other)时,当前存储上已经存在一个处于生存期内的C对象。根据C++标准,在一个对象的生存期尚未结束时,复用其存储位置创建新对象的行为是未定义的。
标准条款的前提是“原对象生存期结束后”才能复用存储创建新对象,而第二次placement new操作时,前一个由第一次placement new创建的对象还在生存期内(我们并没有显式调用它的析构函数),因此直接复用存储创建新对象违反了标准要求,触发未定义行为。后续操作的影响:
一旦触发未定义行为,后续的f()调用以及return *this的行为都不再有定义——程序可能崩溃、输出异常结果,或表现出任何不可预测的行为。
修正建议
如果需要多次替换对象,必须确保每次创建新对象前,先结束当前对象的生存期:
const C& C::operator=( const C& other) { if ( this != &other ) { this->~C(); // 结束原对象生存期 new (this) C(other); // 第一次创建新对象 this->~C(); // 结束第一个新对象的生存期 new (this) C(other); // 第二次创建新对象,此时存储复用前对象生存期已结束 f(); // 此时合法,this指代最新的对象 } return *this; }
内容的提问来源于stack exchange,提问作者minex

