C++20中对const成员使用std::construct_at是否安全?
结论
你给出的写法在C++20及后续标准下是完全合法的,不会触发未定义行为,完全符合透明替换的全部要求。
规则逐条核对
你对透明替换规则的理解方向是对的,我们针对场景里的两个int对象——也就是other原本的const成员i(记为o1)、std::construct_at在原地址创建的新const int对象(记为o2),逐条核对标准要求:
- 8.1 存储完全重叠:
std::construct_at是在&other.i的原始地址原地构造对象,int是基础类型不存在额外内存开销,新对象和旧对象的存储范围完全重合,满足要求。 - 8.2 类型一致(忽略顶层cv限定):o1和o2都是
const int类型,不存在类型差异,满足要求。 - 8.3 o1不是完整const对象:这里很容易踩认知误区——规则里限制的是完整const对象,也就是独立定义的const对象(比如全局/局部定义的
const int a = 0;),而other.i是Obj对象的成员子对象,哪怕自身带const限定,也不属于完整const对象的范畴,满足要求。 - 8.4 不属于潜在重叠子对象:潜在重叠子对象仅指两类:虚基类子对象、加了
[[no_unique_address]]标记的空成员子对象(这类子对象可能和其他成员共享存储地址)。你的i是普通的非静态数据成员,没有任何特殊属性,不存在和其他子对象共享存储的可能,满足要求。 - 8.5 父对象递归满足透明替换:这条的逻辑是,如果替换的是子对象,那么子对象所属的父对象也要满足透明替换要求。在你的代码里,你只销毁重建了
other的成员i,并没有销毁重建other本身——父对象始终是同一个Obj实例,自身替换自身当然满足透明替换规则,因此这条也成立。
关键注意点
- 这个合法结论仅适用于C20及以上版本。C17及更早的标准没有完整的透明替换规则,这种原地重建const成员的写法属于未定义行为。
- 绝对不要用
const_cast<int&>(other.i) = 0这种写法代替destroy+construct:这种写法属于在const对象的生命周期内直接修改其值,是标准明确禁止的未定义行为,和你现在的写法有本质区别。 - 你代码里的执行顺序是安全的:你在构造函数初始化列表中读取
other.i的值初始化新对象的i时,other.i的生命周期还未结束,不存在访问已销毁对象的问题;进入函数体后再销毁重建other.i的顺序完全合法。 - 对于
int这种可平凡析构的类型,std::destroy_at(&other.i)本质上不会执行任何实际操作,只是标记原对象生命周期结束,不会有额外开销。
内容的提问来源于stack exchange,提问作者Joel Niemelä
相关产品推荐
相关产品推荐

