cur*_*guy 6 c++ constants lifetime placement-new language-lawyer
我将不死子句称为 C++ 规则,即在对象销毁后,如果在同一地址创建新对象,有时可以将其视为与旧对象相同的对象。该规则始终存在于 C++ 中,但对附加条件进行了一些更改。
这个问题让我阅读了最新的不死条款。Lifetime [basic.life]/8中修改后的条件是:
(8.1) 新对象的存储正好覆盖原对象所占用的存储位置,并且
嗯,呵呵。位于不同地址的对象不会是同一个对象。
(8.2) 新对象与原始对象的类型相同(忽略顶级 cv 限定符),以及
再说一遍,呵呵。
(8.4) 原始对象和新对象都不是潜在重叠的子对象 ([intro.object])。
它不能是基类、经典类(或具有使其地址不唯一的特殊声明的成员)。再说一遍,呵呵。
(8.3) 原始对象既不是 const 限定的完整对象,也不是此类对象的子对象,并且
现在这很有趣。被替换的对象不能是:
另一方面,被复活的对象可以是:
所以在我看来,所有这些对象x都可以复活:
常量成员子对象
struct CI {
const int x;
};
CI s = { 1 };
new ((void*)&s.x) int(2);
int r = s.x; // OK, 2
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)
const 对象数组中的元素:
const int x[1] = { 1 };
new ((void*)&x[0]) int (2);
int r = x[0];
Run Code Online (Sandbox Code Playgroud)
具有 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
Run Code Online (Sandbox Code Playgroud)
具有引用成员的类:
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
Run Code Online (Sandbox Code Playgroud)
注意:我后来添加了奖励,因为在讨论中提到了将常量放入 ROM。
如果与对象生命周期相关的标准的所有要求都不在[基本生命周期]中,那将是令人惊讶的。
在您引用的标准段落中,“完整”形容词被无意中添加到“对象”名称中的可能性很小。
在论文P0137中,人们可以阅读这一理性(下面@LanguageLawyer 评论中引用的论文):
这是允许 std::Optional 等类型包含 const 子对象所必需的;现有的限制是为了允许 ROMability,因此只影响完整的对象。
为了让我们放心,我们可以验证编译器确实遵循字母中的标准措辞:它们对完整 const 对象执行常量优化,但不对非 const 完整对象的 const 成员子对象执行常量优化:
让我们考虑一下这段代码:
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;
}
Run Code Online (Sandbox Code Playgroud)
当面向 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
Run Code Online (Sandbox Code Playgroud)
返回的值放置在寄存器中eax(根据 ABI 规范:System V x86 处理器特定 ABI):
在函数中,g编译器可以自由地假设x不能在调用时更改,f因为x它是一个完整的 const 对象。因此该值作为立即数 12直接放入寄存器中: 。eaxmov eax, 12
在函数中,h编译器不能随意假设a.m不能在调用时更改,f因为a.m它不是完整 const 对象的子对象。所以调用后f的值a.m必须从内存加载到eax: mov eax, dword ptr [rsp]。