我一直假设一个对象在相同的内存位置开始和结束其生存期,但是最近我遇到了需要确定的情况。具体来说,我正在从标准中寻找一种保证,即无论编译器执行何种优化,构造对象的地址都将与其调用的析构函数相同,并且其析构函数确实是,除非程序终止,否则一定要从该位置调用它。
我一直认为这些东西是理所当然的,但是仔细检查后我找不到保证,而且在复制和移动省略周围存在一些语言,我不确定该如何解释。我希望这里的一些熟悉标准的人可以将我引向章节和经文。
答案 0 :(得分:15)
您要查找的内容在[intro.object]/1
中定义[...]对象在其构造周期([class.cdtor])的整个生命周期内以及在其破坏期间([class.cdtor])占据一个存储区域。
这意味着地址只要您可以访问就无法更改。
答案 1 :(得分:1)
具体地说,我正在从标准中寻找一种保证,即无论编译器执行何种优化,构造对象的地址都与从其调用析构函数的对象相同...
并且确实保证可以从该位置调用其析构函数,除非程序终止。
该标准保证自动变量和static
变量都可以,只要它们不会对对象造成不良影响即可。但是,它也不保证从免费存储区分配的对象。
即使对于自动变量,狡猾的程序员也可以通过指针操纵来颠覆意图,并通过指针显式调用析构函数。
此外,当基类没有delete
的析构函数时,virtual
-基类指针的调用将调用错误的析构函数。这将是编程错误,而不是意图颠覆的结果。
示例:
struct Base
{
int b;
};
struct Derived : virtual Base
{
float d;
};
int main()
{
{
Derived d1; // Not a problem.
}
{
Derived d1;
Derived* ptr = &d1;
delete ptr; // Bad. The programmer subverts the program.
// Must not use delete.
}
{
Derived* d2 = new Derived; // The destructor does not get called automatically.
}
{
Derived* d2 = new Derived;
delete d2; // OK. The proper destructor gets called.
}
{
Derived* d2 = new Derived;
Base* ptr = d2;
delete ptr; // Programmer error. The wrong destructor gets called.
}
}
答案 2 :(得分:-1)
如内森·奥利弗({@ 3}}所述:
[...]对象在其构造周期([class.cdtor])的整个生命周期内以及在其破坏期间([class.cdtor])占据一个存储区域。
编译器尊重这一点,并且在某些对象(类似于您描述的对象)中,它必须为真。考虑std::mutex
。互斥锁不能被复制或移动,其原因是,互斥锁必须在其生命周期内保持在内存中的同一位置才能正常工作。
复制/移动省略通过在需要移动的地方创建对象而起作用。就这么简单。
我们可以自己看到这种行为:
#include <iostream>
struct Foo {
Foo() {
std::cout << "I am at " << (void*)this << '\n';
}
// Delete copy and move, to ensure it cannot be moved
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
};
Foo getFoo() {
return Foo();
}
int main() {
Foo* ptr = new Foo(getFoo());
std::cout << "Foo ptr is at " << (void*)ptr << '\n';
delete ptr;
}
此代码输出:
I am at 0x201ee70
Foo ptr is at 0x201ee70
并且我们看到Foo
在其生命周期内始终位于同一位置,甚至没有被复制或移动,即使是在动态分配的内存中创建的。 >
如果一个函数返回的类型不能被普通地复制,则该函数采用一个隐式参数,该参数表示应该构造返回值的内存地址。