В следующем коде X зарегистрирован в глобальном контейнере, который становится его общим владельцем. Деструктор X проверяет, что он больше не является частью такого владения, что, как я ожидаю, будет действительным предварительным условием уничтожения.
#include <vector>
#include <memory>
struct X {
~X();
};
std::vector<std::shared_ptr<X> > global_x_reg;
X::~X()
{
for (auto iter = global_x_reg.begin(), end = global_x_reg.end(); iter != end; ++iter)
if (iter->get() == this)
throw "Oops. X gets destroyed while it is still owned!";
}
int main(int argc, char** argv)
{
global_x_reg.push_back( std::shared_ptr<X>(new X) );
global_x_reg.clear(); // calls X::~X().
}
Когда он запускается (после компиляции с VS2010), «Oops …» выбрасывается при очистке контейнера.
Вопросы:
clear()
таким образом, что при разрушении его значений эти значения больше не видны как загрязнители. std::shared_ptr::get
, когда std::shared_ptr
разрушает свой заемщик, возвращение nullptr
?Согласно N3936 [basic.life] / 1: «Время жизни объекта с нетривиальной инициализацией заканчивается, когда начинается вызов деструктора.», И / 3:
Свойства, приписываемые объектам в настоящем международном стандарте, применяются к данному объекту только в течение срока его службы. [ Замечания: В частности, перед началом срока службы объекта и после его окончания существуют значительные ограничения на использование объекта, как описано ниже, в 12.6.2 и 12.7. Кроме того, поведение строящегося и разрушаемого объекта может не совпадать с поведением объекта, время жизни которого началось, а не закончилось. 12.6.2 и 12.7 описывают поведение объектов на этапах строительства и разрушения. —конечная нота ]
Вы вызываете функцию-член на shared_ptr
после окончания его жизни. Поскольку нет способа узнать, соответствует ли данная функция-член класса стандартной библиотеки указанным ограничениям, вызов функции-члена для объекта стандартной библиотеки после окончания ее срока службы, следовательно, будет иметь неопределенное поведение, если не указано иное.
Смотрите также Выпуск 2382 рабочей группы библиотеки «Неясный порядок обновления контейнера и уничтожение объекта при удалении объекта», что очень относится к вопросу. В общем случае, не следует повторно вводить стандартный объект библиотеки (global_x_reg
) пока он находится в середине обработки вызова функции-члена (global_x_reg.clear()
в этом случае). Очевидно, что инварианты класса должны храниться до и после вызова члена, но нет никакой гарантии, что объект находится в допустимом состоянии. в течение вызов.
Я не думаю, что есть какая-то конкретная причина .get()
должен вернуть NULL
перед удалителем (и, следовательно, ~X()
) вызывается std::shared_ptr<X>::~shared_ptr<X>
, Единственное требование — чтобы он вернулся NULL
один раз std:shared_ptr<X>::~shared_ptr<X>
завершено.
Точно так же, если std::vector
использует размещение новых для создания и уничтожения его элементов (и это будет), нет никаких причин, что это должен обновили свои методы доступа перед уничтожением размещенного элемента.
На самом деле, если вы думаете об этом обычном старомодном шаблоне:
T* ptr = new T();
delete ptr;
ptr = NULL;
Понятно, что ~T()
будет вызван раньше ptr
установлен в NULL
; это почти то же самое, что вы видите.
Следующая выдержка из libstdc ++ v4.6.3 поддерживает аналогию:
00353 virtual void
00354 _M_destroy() // nothrow
00355 {
00356 _My_alloc_type __a(_M_del);
00357 this->~_Sp_counted_deleter();
00358 __a.deallocate(this, 1);
00359 }
Короче говоря, вы наблюдаете совершенно безобидную деталь реализации и пытаетесь утверждать, что она нарушает заявленную семантику, когда я не думаю, что это так.
Я думаю, что вы делаете странную вещь здесь. Если вы хотите сохранить доступ к своим элементам, вы должны реализовать X::~X()
таким образом, что уничтоженный объект является незарегистрированным, но не очищает сам std :: vector. Кроме того, общий указатель становится недействительным (и X уничтожается) внутри вызова clear()
из std::vector
обсуждаемый. Следовательно std::vector
находится в хрупком состоянии, и я бы в любом случае не полагался на это слишком сильно. Рассмотрим пример связанного списка: если вы удаляете элемент, который уничтожен в ходе выполнения, и деструктор выполняет итерации по списку, это может быть в том случае, если ссылки на узлы не находятся в согласованном состоянии. Я бы избежал этой ситуации.