я написал Эта статья и получил несколько комментариев, которые смутили меня.
Это в основном сводится к тому, что я видел 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
используется?
По словам Херба Саттера в ПОЛУЧИЛО № 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()
{
}
Следующий пример — попытка продемонстрировать разницу между 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
в первом случае вы не узнаете до времени выполнения, что у вас есть какая-то отладка (по модулю необязательных предупреждений, которые может выдавать ваш компилятор). С последним вы гарантированно узнаете во время компиляции.