Есть ли ситуации, в которых хранилище объекта может измениться в течение срока его службы?
Я всегда предполагал, что объект начинается и заканчивает свое время жизни в одной и той же ячейке памяти, но недавно я столкнулся со сценарием, в котором мне нужно быть уверенным. В частности, я ищу гарантию от стандарта, что независимо от того, какие оптимизации выполняет компилятор, адрес, по которому строится объект, является тем же, из которого вызывается деструктор, из которого... и что его деструктор действительно гарантированно будет вызван из этого места, если программа не завершается.
Я всегда воспринимал это как должное, но при ближайшем рассмотрении я не могу найти гарантию, и есть некоторый язык вокруг копирования и изменения, который я не знаю, как интерпретировать. Я надеюсь, что некоторые из более знающих людей здесь могут указать мне на главу и стих.
Ответы
Ответ 1
То, что вы ищете, определено в [intro.object]/1
[...] Объект занимает область хранения в период его строительства ([class.cdtor]), в течение всего срока его службы и в период разрушения ([class.cdtor]).
Это означает, что адрес не может измениться, пока вы можете получить к нему доступ.
Ответ 2
В частности, я ищу гарантию от стандарта, что независимо от того, какие оптимизации выполняет компилятор, адрес, по которому строится объект, будет одним и тем же, с которого его деструктор будет вызываться из...
и что его деструктор действительно гарантированно будет вызван из этого места, если программа не завершает работу.
Стандарт гарантирует как автоматические переменные, так и переменные static
, если с объектами не происходит ничего плохого. Тем не менее, это не гарантирует ни для объектов, выделенных из бесплатного магазина.
Даже для автоматических переменных хитрый программист может подорвать намерение с помощью манипуляции с указателем и явного вызова деструктора с помощью указателя.
Кроме того, неправильный деструктор будет вызываться, когда delete
-ing указатель базового класса, когда базовый класс не имеет деструктора 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.
}
}
Ответ 3
Как упоминает Натан Оливер, стандарт гласит, что:
[...] Объект занимает область хранения в период его строительства ([class.cdtor]), в течение всего срока его службы и в период разрушения ([class.cdtor]).
Компиляторы уважают это, и есть объекты (похожие на те, которые вы описываете), для которых это должно выполняться. Рассмотрим std::mutex
. Мьютекс не может быть скопирован или перемещен, и причина этого заключается в том, что он должен оставаться в одном и том же месте в памяти в течение всего времени его существования, чтобы работать.
Так как же работает копирование/перемещение elision?
Копирование/перемещение elision работает, создавая объект, куда он должен идти. Это так просто.
Мы можем увидеть это поведение для себя:
#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
остается в одном и том же месте в течение всего срока его существования, даже не копируя и не перемещая, даже если он создается в динамически выделяемой памяти.
Как компилятор узнает, где создать объект?
Если функция возвращает тип, который нетривиально копируется, то эта функция принимает неявный параметр, представляющий адрес памяти, по которому она должна создать возвращаемое значение.