Следует ли завершить владение до или после того, как контейнеры stl вызовут деструктор своего значения?

В следующем коде 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 …» выбрасывается при очистке контейнера.

Вопросы:

  1. этот код законен? Если нет, то почему нет? Если так, то должно ли это бросить?
  2. должен реализовывать стандартный контейнер clear() таким образом, что при разрушении его значений эти значения больше не видны как загрязнители.
  3. должен std::shared_ptr::get, когда std::shared_ptr разрушает свой заемщик, возвращение nullptr?

5

Решение

Согласно 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() в этом случае). Очевидно, что инварианты класса должны храниться до и после вызова члена, но нет никакой гарантии, что объект находится в допустимом состоянии. в течение вызов.

4

Другие решения

Я не думаю, что есть какая-то конкретная причина .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       }

(ссылка на сайт)

Короче говоря, вы наблюдаете совершенно безобидную деталь реализации и пытаетесь утверждать, что она нарушает заявленную семантику, когда я не думаю, что это так.

2

Я думаю, что вы делаете странную вещь здесь. Если вы хотите сохранить доступ к своим элементам, вы должны реализовать X::~X() таким образом, что уничтоженный объект является незарегистрированным, но не очищает сам std :: vector. Кроме того, общий указатель становится недействительным (и X уничтожается) внутри вызова clear() из std::vector обсуждаемый. Следовательно std::vector находится в хрупком состоянии, и я бы в любом случае не полагался на это слишком сильно. Рассмотрим пример связанного списка: если вы удаляете элемент, который уничтожен в ходе выполнения, и деструктор выполняет итерации по списку, это может быть в том случае, если ссылки на узлы не находятся в согласованном состоянии. Я бы избежал этой ситуации.

0
По вопросам рекламы [email protected]