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

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

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

struct T2;

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

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

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

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

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

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

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

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

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

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

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

[C++11: 20.7.1.1.1]:

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

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

Но, default_delete«s operator() требует полного типа:

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


Я полагаю, мой вопрос заключается в следующем:

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

struct T2;

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

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

12

Решение

По словам Херба Саттера в ПОЛУЧИЛО № 100, unique_ptr страдает от той же проблемы, что и auto_ptr в отношении неполных типов.

…хотя и unique_ptr и shared_ptr могут быть созданы с помощью
неполный тип, деструктору unique_ptr требуется полный тип в
Чтобы вызвать удаление …

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

// T1.h
struct T2;

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

// T1.cpp
#include "T2.h"
T1::~T1()
{
}
9

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

Следующий пример — попытка продемонстрировать разницу между 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;
}

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

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

8

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