Устанавливает ли std :: unique_ptr свой указатель на nullptr внутри деструктора? - PullRequest
0 голосов
/ 17 января 2019

При реализации моего unique_ptr (просто для удовольствия) я обнаружил, что он не может пройти этот тестовый файл из libstdcxx:

struct A;

struct B
{
  std::unique_ptr<A> a;
};

struct A
{
  B* b;
  ~A() { VERIFY(b->a != nullptr); }
};

void test01()
{
  B b;
  b.a.reset(new A);
  b.a->b = &b;
}

gcc успешно проходит этот тестовый файл (конечно, этот файл из libstdcxx), в то время как clang не удается для части VERIFY.

Вопрос:

  1. Это зависящее от реализации или неопределенное поведение?
  2. Полагаю, это постусловие (b->a != nullptr) важно для gcc, иначе у него не будет тестового файла, но я не знаю, что за этим стоит. Это связано с оптимизацией? Я знаю, что многие UB для лучшей оптимизации.

Ответы [ 2 ]

0 голосов
/ 17 января 2019

Нет никаких требований к конечному состоянию памяти, занимаемой std::unique_ptr<> после уничтожения. Не имеет смысла устанавливать его в ноль, поскольку память возвращается туда, откуда она была выделена. GCC, вероятно, проверяет, что это не ноль, чтобы убедиться, что никто не добавил ненужный код для его очистки. При правильных обстоятельствах принудительное удаление значения, когда оно не требуется, может привести к снижению производительности.

0 голосов
/ 17 января 2019

clang (libc ++) кажется несовместимым по этому вопросу, потому что стандарт гласит:

[unique.ptr.single.dtor]

~unique_ptr();
  1. Требуется: Выражение get_­deleter()(get()) должно быть правильно сформированным, иметь четко определенное поведение и не создавать исключений. [ Примечание: Использование default_­delete требует, чтобы T был полным типом. - Конечная нота ]

  2. Эффекты: если get() == nullptr, то никаких эффектов нет. В противном случае get_­deleter()(get()).

Таким образом, деструктор должен быть эквивалентен get_deleter()(get()), что подразумевает, что b->a не может быть nullptr в деструкторе A (который вызывается внутри get_deleter() инструкцией delete).


В примечании сторон, clang (libc ++) и gcc (libstdc ++) устанавливают указатель на nullptr при уничтожении std::unique_ptr, но деструктор gcc:

auto& __ptr = _M_t._M_ptr();
if (__ptr != nullptr)
    get_deleter()(__ptr);
__ptr = pointer();

... а вот clang (вызов reset()):

pointer __tmp = __ptr_.first();
__ptr_.first() = pointer();
if (__tmp)
   __ptr_.second()(__tmp);

Как видите, gcc сначала удаляет, затем присваивает nullptr (pointer()), тогда как clang сначала присваивает nullptr (pointer()), затем удаляет 1 .


1 pointer - псевдоним, соответствующий Deleter::pointer, если он существует, или просто T*.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...