Подтвердить что ты не робот

Верно ли, что объявление unique_ptr, в отличие от объявления auto_ptr, хорошо определено, когда тип его шаблона имеет неполный тип?

Я написал эту статью и получил некоторые комментарии по этому поводу, которые смутили меня.

В основном это сводится к тому, что я видел, что T2 используется только как параметр шаблона и ошибочно подскочил к выводу, что поэтому я мог бы воспользоваться возможностью объявления вперед:

struct T2;

struct T1
{
    std::auto_ptr<T2> obj;
};

Это вызывает UB, если я не буду определять T2 где-то в одном TU, потому что std::auto_ptr<T2> вызывает delete на своей внутренней T2* и вызов delete указателя на объект неполного типа, полный тип которого имеет нетривиальный деструктор undefined:

[C++11: 5.3.5/5]: Если удаляемый объект имеет неполный тип класса в точке удаления, а полный класс имеет нетривиальный деструктор или функцию освобождения, поведение undefined.

Инструментарий GCC, с которым я столкнулся, использовал — v4.3.3 (Sourcery g++ Lite 2009q1-203) — был достаточно любезен, чтобы сообщить мне с примечанием:

note: ни деструктор, ни оператор-оператор класса не будут вызваны, даже если они объявлены при определении класса.

хотя, похоже, трудно получить эту диагностику в других версиях GCC.

Моя проблема заключалась в том, что было бы намного легче обнаружить такую ​​ошибку, если бы delete указатель на экземпляр неполного типа был плохо сформирован, а не UB, но это похоже на неразрешимую проблему для для решения, поэтому я понимаю, почему это UB.

Но потом мне сказали, что если бы я использовал std::unique_ptr<T2>, это было бы безопасно и совместимо.

n3035 якобы говорит в 20.9.10.2:

Параметр шаблона T of unique_ptr может быть неполным.

Все, что я могу найти в С++ 11, это:

[C++11: 20.7.1.1.1]:

/1 Шаблон класса default_delete используется как дефолт по умолчанию (политика уничтожения) для шаблона класса unique_ptr.

/2 Параметр шаблона T of default_delete может быть неполным.

Но для default_delete operator() требуется полный тип:

[C++11: 20.7.1.1.2/4]: Если T является неполным, программа плохо сформирована.


Я полагаю, что мой вопрос таков:

Являются ли комментаторы моей статьи правильными, говоря, что единица перевода, состоящая только из следующего кода, хорошо сформирована и четко определена? Или они ошибаются?

struct T2;

struct T1
{
    std::unique_ptr<T2> obj;
};

Если они верны, как ожидается, что компилятор выполнит это, учитывая, что есть веские причины для того, чтобы быть UB, по крайней мере, когда используется std::auto_ptr?

4b9b3361

Ответ 1

Согласно Herb Sutter в GOTW # 100, unique_ptr страдает от той же проблемы, что и auto_ptr по отношению к неполным типам.

... хотя и unique_ptr, и shared_ptr могут быть созданы с помощью неполный тип, destructor unique_ptrs требует полного типа в чтобы вызвать delete...

Его предложение состоит в том, чтобы объявить деструктор вашего содержащего класса (т.е. T1) в файле заголовка, затем поместить его определение в блок перевода, в котором T2 является полным типом.

// T1.h
struct T2;

struct T1
{
  ~T1();
  std::unique_ptr< T2 >;
};

// T1.cpp
#include "T2.h"

T1::~T1()
{
}

Ответ 2

Следующий пример - попытка продемонстрировать разницу между std::auto_ptr<T> и std::unique_ptr<T>. Сначала рассмотрим эту программу, состоящую из 2 исходных файлов и 1 заголовка:

Заголовок:

// test.h

#ifndef TEST_H
#define TEST_H

#include <memory>

template <class T>
using smart_ptr = std::auto_ptr<T>;

struct T2;

struct T1
{
    smart_ptr<T2> obj;

    T1(T2* p);
};

T2*
source();

#endif  // TEST_H

Первый источник:

// test.cpp

#include "test.h"

int main()
{
    T1 t1(source());
}

Второй источник:

// test2.cpp

#include "test.h"
#include <iostream>


struct T2
{
    ~T2() {std::cout << "~T2()\n";}
};

T1::T1(T2* p)
    : obj(p)
{
}

T2*
source()
{
    return new T2;
}

Эта программа должна компилироваться (она может компилироваться с предупреждением, но она должна компилироваться). Но во время выполнения он демонстрирует поведение undefined. И это, вероятно, не будет выводиться:

~T2()

который указывает, что деструктор T2 не запущен. По крайней мере, это не в моей системе.

Если я изменю test.h на:

template <class T>
using smart_ptr = std::unique_ptr<T>;

Затем компилятор должен вывести диагностику (ошибку).

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

Чтобы исправить ошибку времени компиляции, вы должны указать ~T1() после завершения T2. В test2.cpp добавьте после T2:

T1::~T1() = default;

Теперь он должен компилироваться и выводиться:

~T2()

Вероятно, вы захотите также объявить и очертить элементы перемещения:

T1::T1(T1&&) = default;
T1& T1::operator=(T1&&) = default;

Вы можете сделать эти же исправления с помощью auto_ptr, и это будет снова правильно. Но опять-таки различие между auto_ptr и unique_ptr заключается в том, что с первым вы не обнаружите до тех пор, пока не будет выполнено время отладки (по модулю необязательные предупреждения, которые может дать ваш компилятор). С последним вы гарантированно узнаете во время компиляции.