【问题标题】:When is a type considered complete?什么时候一个类型被认为是完整的?
【发布时间】:2014-07-19 13:47:41
【问题描述】:

考虑以下代码 sn-p。 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&lt;Widget&gt; 的析构函数时类 Widget 不完整。但是,这可以在 g++ 4.8 和 Visual Studio 2010 上干净地编译。请注意主函数中带有 sizeof(Widget) 表达式的注释语句。如果我取消注释它,它将无法编译,这意味着 Widget 在那时一定是不完整的。

对此行为的正确解释是什么?

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

【问题讨论】:

  • Here is the code in action;就像你一样,我希望 check_delete 能够捕捉到不完整类型的使用,所以我很惊讶这个编译与 sizeof 评论。
  • @MatthieuM.:明白了。
  • 我认为区别在于你是写struct SP { ~SP() { depete ptr; } /* ... */ };还是struct SP { ~SP(); /* ... */ };。如果析构函数不是内联的,那么在调用它的时候就不需要知道它,因此也不需要ptr 的类型。

标签: c++ boost smart-pointers incomplete-type scoped-ptr


【解决方案1】:

5.3.5 删除[expr.delete]

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

因此,您肯定会认为它是 UB,因为 Widget::~Widget() 是不平凡的,并且您会希望安全保护在 boost 中出错。

现在,让我们深入挖掘:

2.2 翻译阶段[lex.phases]

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

翻译阶段拯救了您:
翻译翻译单元,然后实例化模板...

【讨论】:

  • 为什么checked_delete没有失败? checked_delete 仅在类型完整时才能正常工作。这正是针对这种情况的保障。
  • 感谢您让我思考这些问题。
  • 粗略地说,实例化可能会推迟到对翻译单元进行全面扫描。对于这样的实现,在翻译单元的任何地方定义前向声明的类型与在前向声明之后的任何地方都可以使用定义一样好。
  • 这也是我通过将类 defs 移出 TU 发现的
  • @CppNoob:不可能。所有符合要求的实现都必须将模板实例化推迟到翻译结束。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-23
  • 2017-12-11
  • 1970-01-01
相关资源
最近更新 更多