Когда тип считается завершенным?

Рассмотрим следующий фрагмент кода. Деструктор boost::scoped_ptr вызывается в конце основной функции. Деструктор использует boost::checked_delete для освобождения инкапсулированного указателя виджета.

#include <boost/scoped_ptr.hpp>
#include <iostream>

class Widget;
Widget *make_widget();

int main()
{  
  boost::scoped_ptr<Widget> sp(make_widget());
  // std::cout << sizeof(Widget) << std::endl;
}

class Widget
{
public:
  Widget() {}
  ~Widget() { std::cout << "Widget destructor called." << std::endl; }
};

Widget *make_widget()
{
  return new Widget;
}

Я ожидал, что этот код не скомпилируется, поскольку класс Widget неполный в момент вызова деструктора scoped_ptr<Widget>. Однако это чисто компилируется в g++ 4.8 и Visual Studio 2010. Обратите внимание на закомментированный оператор с выражением sizeof(Widget) в основной функции. Если я раскомментирую его, он не сможет скомпилироваться, что означает, что Widget должен быть неполным в этот момент.

Каково правильное объяснение такого поведения?

РЕДАКТИРОВАТЬ: некоторые ответы (теперь удаленные) указывали на поведение undefined, но я ожидал, что использование checked_delete в деструкторе scoped_ptr вызовет сбой компиляции. FWIW, я использую Boost 1.55.


person CppNoob    schedule 19.07.2014    source источник
comment
Вот код в действии; как и вы, я ожидал, что check_delete поймает использование неполного типа, поэтому я очень удивлен этой компиляцией с комментариями sizeof.   -  person Matthieu M.    schedule 19.07.2014
comment
@MatthieuM.: Понятно.   -  person Deduplicator    schedule 19.07.2014
comment
Я думаю, что разница заключается в том, пишете ли вы struct SP { ~SP() { depete ptr; } /* ... */ }; или struct SP { ~SP(); /* ... */ };. Если деструктор не является встроенным, то его не нужно знать в момент вызова, поэтому тип ptr также не нужен.   -  person Kerrek SB    schedule 19.07.2014


Ответы (1)


5.3.5 Удалить [expr.delete]

5 Если удаляемый объект имеет неполный тип класса в момент удаления, а полный класс имеет нетривиальный деструктор или функцию освобождения, поведение не определено.

Таким образом, вы, безусловно, ожидаете, что это будет UB, поскольку Widget::~Widget() не является тривиальным, и вы ожидаете, что защитный механизм в форсировании выдаст ошибку.

Теперь копнем выше:

2.2 Этапы перевода [lex.phases]

8 Переведенные единицы перевода и единицы воплощения объединяются следующим образом: [Примечание: ... ] Каждая переведенная единица перевода проверяется для получения списка требуемых воплощений. [ Примечание. Это может включать экземпляры, которые были запрошены явно (14.7.2). —конец примечания ] Определения необходимых шаблонов расположены. Реализация определяет, требуется ли доступность источника единиц перевода, содержащих эти определения. [ Примечание. Реализация может закодировать достаточно информации в переведенную единицу перевода, чтобы убедиться, что источник здесь не требуется. —конец примечания ] Все необходимые инстанцирования выполняются для создания единиц инстанцирования. [ Примечание. Они аналогичны переведенным единицам перевода, но не содержат ссылок на неконкретизированные шаблоны и не содержат шаблонов. определения. —конец примечания ] Программа неправильно сформирована, если какой-либо экземпляр завершается сбоем.

Вы спасены этапами перевода:
переведите единицу перевода, затем создайте экземпляры шаблонов...

person Deduplicator    schedule 19.07.2014
comment
Почему не Checked_delete терпит неудачу? checked_delete работает корректно только тогда, когда тип завершен. Это гарантия именно для таких ситуаций. - person CppNoob; 19.07.2014
comment
Спасибо, что заставил меня задуматься об этих углах. - person Deduplicator; 19.07.2014
comment
Грубо говоря, инстанцирование может быть отложено до полного сканирования единицы трансляции. Для таких реализаций определение предварительно объявленного типа в любом месте единицы трансляции так же хорошо, как сделать определение доступным везде после прямого объявления. - person CppNoob; 19.07.2014
comment
Это то, что я также обнаружил, переместив определения классов из TU. - person sehe; 19.07.2014
comment
@CppNoob: Не может. Все соответствующие реализации должны отложить создание экземпляра шаблона до конца трансляции. - person Deduplicator; 19.07.2014