8

考虑以下代码片段。boost::scoped_ptr 的析构函数在 main 函数结束时被调用。析构函数使用 boost::checked_delete 来释放封装的 Widget 指针。

#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;
}

我预计此代码无法编译,因为在scoped_ptr<Widget>调用析构函数时类 Widget 不完整。sizeof(Widget)但是,这可以在 g++ 4.8 和 Visual Studio 2010 上干净地编译。请注意主函数中带有表达式的注释语句。如果我取消注释它,它将无法编译,这意味着Widget此时必须是不完整的。

这种行为的正确解释是什么?

编辑:一些答案(现已删除)指向未定义的行为,但我预计在scoped_ptr析构函数中使用 checked_delete 会导致编译失败。FWIW,我正在使用 Boost 1.55。

4

1 回答 1

4

5.3.5 删除[expr.delete]

5 如果被删除的对象在删除点具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为未定义。

因此,您当然会期望它是 UB,因为Widget::~Widget()这不是微不足道的,并且您会期望提升中的保护措施会出错。

现在,让我们深入挖掘:

2.2 翻译阶段[lex.phases]

8 个翻译后的翻译单元和实例化单元组合如下:[注意:...]检查每个翻译后的翻译单元以生成所需实例化的列表。[注意:这可能包括已明确请求的实例化(14.7.2)。—尾注]所需模板的定义位于。是否需要包含这些定义的翻译单元的源是由实现定义的。[注意:实现可以将足够的信息编码到翻译的翻译单元中,以确保此处不需要源。—尾注] 执行所有必需的实例化以生成实例化单元。 [注意:这些类似于翻译的翻译单元,但不包含对未实例化模板的引用和模板定义。— 尾注]如果任何实例化失败,则程序是非良构的。

您被翻译阶段保存:
翻译翻译单元,然后实例化模板......

于 2014-07-19T13:53:34.083 回答