8

Consider the following code snippet. The destructor of boost::scoped_ptr is invoked at the end of the main function. The destructor uses boost::checked_delete to deallocate the encapsulated Widget pointer.

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

I expected this code to fail to compile as the class Widget is incomplete at the point the destructor of scoped_ptr<Widget> is invoked. However this compiles cleanly on g++ 4.8 and Visual Studio 2010. Note the commented statement with the sizeof(Widget) expression in the main function. If I uncomment it, it will fail to compile implying that Widget must be incomplete at that point.

What is the correct explanation for this behavior?

EDIT: Some answers (now deleted) pointed to undefined behavior but I would have expected the use of checked_delete in scoped_ptr's destructor to cause a compilation failure. FWIW, I'm using Boost 1.55.

CppNoob
  • 2,322
  • 1
  • 24
  • 35
  • 1
    [Here is the code in action](http://coliru.stacked-crooked.com/a/103312c912323fca); much like you I would have expected `check_delete` to catch the use of an incomplete type so I am pretty surprised this compile with `sizeof` commented. – Matthieu M. Jul 19 '14 at 14:06
  • @MatthieuM.: Got it explained. – Deduplicator Jul 19 '14 at 14:11
  • I think the difference lies in whether you write `struct SP { ~SP() { depete ptr; } /* ... */ };` or `struct SP { ~SP(); /* ... */ };`. If the destructor isn't inline, then it doesn't need to be known at the time where it's called, and so the type of `ptr` is also not needed. – Kerrek SB Jul 19 '14 at 15:08

1 Answers1

4

5.3.5 Delete [expr.delete]

5 If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.

Thus, you certainly would expect it to be UB, as Widget::~Widget() is non-trivial, and you would expect the safe-guard in boost to error out.

Now, let's dig higher:

2.2 Phases of translation [lex.phases]

8 Translated translation units and instantiation units are combined as follows: [ Note: ... ] Each translated translation unit is examined to produce a list of required instantiations. [ Note: This may include instantiations which have been explicitly requested (14.7.2). —end note ] The definitions of the required templates are located. It is implementation-defined whether the source of the translation units containing these definitions is required to be available. [ Note: An implementation could encode sufficient information into the translated translation unit so as to ensure the source is not required here. —end note ] All the required instantiations are performed to produce instantiation units. [ Note: These are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. —end note ] The program is ill-formed if any instantiation fails.

You are saved by phases of translation:
Translate the translation unit, then instantiate templates...

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • 1
    Why doesn't checked_delete fail? checked_delete works correctly only when the type is complete. It is a safeguard precisely for such situations. – CppNoob Jul 19 '14 at 13:55
  • Thanks for making me think around those corners. – Deduplicator Jul 19 '14 at 14:13
  • So roughly speaking, the instantiations may be deferred till a full scan of the translation unit. For such implementations, defining a forward declared type anywhere in the translation unit is as good as making the definition available everywhere after the forward declaration. – CppNoob Jul 19 '14 at 14:17
  • 1
    This is what I also found by moving the class defs out of the TU – sehe Jul 19 '14 at 14:19
  • 3
    @CppNoob: Not may. All conforming implementations must defer template instantiation until the end of translation. – Deduplicator Jul 19 '14 at 14:27