“亡灵”条款
我将 undead 子句称为 C++ 规则,即在对象销毁后,如果在同一地址创建新对象,则有时可以将其视为与旧对象相同的对象。该规则始终存在于 C++ 中,但对附加条件进行了一些更改。
我被 this question 要求阅读最新的不死条款。 Lifetime [basic.life]/8 中的修改条件为:
(8.1) the storage for the new object exactly overlays the storage location which the original object occupied, and
嗯,呃。不同地址的对象不会是同一个对象。
(8.2) the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
再说一次,呵呵。
(8.4) neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).
它不能是基类、经典类(或具有使其地址不唯一的特殊声明的成员)。再说一次,呵呵。
(8.3) the original object is neither a complete object that is const-qualified nor a subobject of such an object, and
现在这很有趣。被替换的对象不能是:
另一方面,被复活的对象可以是:
常量子对象
所以在我看来,所有这些对象
x
都可以复活:const 成员子对象
struct CI {
const int x;
};
CI s = { 1 };
new ((void*)&s.x) int(2);
int r = s.x; // OK, 2
const 成员的子对象:
struct T {
int x;
};
struct CT {
const T m = { 1 };
};
CT s;
new ((void*)&s.m.x) int (2);
int r = s.m.x;
const 对象数组中的元素:
const int x[1] = { 1 };
new ((void*)&x[0]) int (2);
int r = x[0];
具有 const 和 reference 成员的类
带有 const 或引用成员的类类型的对象似乎也不被禁止;复活的对象仍称为
x
。具有 const 成员的类:
struct CIM {
CIM(int i): m(i) {}
const int m;
};
CIM x(1);
new ((void*)&x) CIM(2);
int r = x.m; // OK, 2
具有引用成员的类:
struct CRM {
CRM (int &r): m(r) {}
int &m;
};
int i=1,j=2;
CRM x(i);
new ((void*)&x) CRM(j);
int r = x.m; // OK, 2
问题
注意:我后来添加了奖金,因为在讨论中出现了将常量放入 ROM 的问题。
最佳答案
如果与对象生命周期相关的标准的所有要求都不在 [basic-life] 中,那将是令人惊讶的。
在您引用的标准段落中,“完整”形容词被无意添加到名称“对象”的可能性很小。
在论文P0137 ,人们可以阅读这个理性(下面@LanguageLawyer 评论中引用的论文):
This is necessary to allow types such as std::optional to contain const subobjects; the existing restriction exists to allow ROMability, and so only affects complete objects.
为了让我们放心,我们可以验证编译器确实遵循信中的标准措辞:它们对完整的 const 对象执行常量优化,但对非 const 完整对象的 const 成员子对象不执行优化:
让我们考虑this code :
struct A{const int m;};
void f(const int& a);
auto g(){
const int x=12;
f(x);
return x;
}
auto h(){
A a{12};
f(a.m);
return a.m;
}
当面向 x86_64 时,Clang 和 GCC 都会生成此程序集:
g(): # @g()
push rax
mov dword ptr [rsp + 4], 12
lea rdi, [rsp + 4]
call f(int const&)
mov eax, 12 ;//the return cannot be anything else than 12
pop rcx
ret
h(): # @h()
push rax
mov dword ptr [rsp], 12
mov rdi, rsp
call f(int const&)
mov eax, dword ptr [rsp] //the content of a.m is returned
pop rcx
ret
返回值放入寄存器
eax
(根据 ABI 规范:System V x86 处理器特定 ABI):g
编译器可以自由假设 x
无法通过对 f
的调用进行更改因为x
是一个完整的 const 对象。所以值 12
直接放在eax
注册为立即值:mov eax, 12
. h
编译器不能随意假设 a.m
无法通过对 f
的调用进行更改因为a.m
不是完整的 const 对象的子对象。所以在调用 f
之后a.m
的值必须从内存加载到 eax
: mov eax, dword ptr [rsp]
. 关于c++ - 不死物体 ([basic.life]/8) : why is reference rebinding (and const modification) allowed?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59298904/