3

Suppose I have a public class and a private implementation class (e.g. PIMPL pattern), and I wish to wrap the private class with a template smart pointer class with a checked delete, as follows:

PublicClass.h

class PrivateClass;

// simple smart pointer with checked delete
template<class X> class demo_ptr
{
public:
    demo_ptr (X* p) : the_p(p) { }
    ~demo_ptr () {
        // from boost::checked_delete: don't allow compilation of incomplete type
        typedef char type_must_be_complete[ sizeof(X)? 1: -1 ];
        (void) sizeof(type_must_be_complete);
        delete the_p;
    }
private:
    X* the_p;
};

// public-facing class that wishes to wrap some private implementation guts
class PublicClass
{
public:
    PublicClass();
    ~PublicClass();

private:
    demo_ptr<PrivateClass> pvt;
};

PublicClass.cpp

#include "PublicClass.h"

class PrivateClass
{
public:
    // implementation stuff goes here...
    PrivateClass() {}
};
//---------------------------------------------------------------------------
PublicClass::PublicClass() : pvt(new PrivateClass()) {}

PublicClass::~PublicClass() {}

main.cpp

#include "PublicClass.h"

int main()
{
    PublicClass *test = new PublicClass();
    delete test;
    return 0;
}

This code compiles successfully on Visual C++ 2008, but fails to compile on an old version of C++ Builder. In particular, main.cpp does not compile because demo_ptr<PrivateClass>::~demo_ptr is being instantiated by main.cpp, and that destructor won't compile because it can't do sizeof on an incomplete type for PrivateClass. Clearly, it is not useful for the compiler to be instantiating ~demo_ptr in the consuming main.cpp, since it will never be able to generate a sensible implementation (seeing as how ~PrivateClass is not accessible). (PublicClass.cpp compiles fine on all tested compilers.)

My question is: what does the C++ standard say about implicit instantiation of a template class's member functions? Might it be one of the following? In particular, has this changed over the years?

  • If a template class is used, then all member functions of the class should be implicitly instantiated - whether used or not?
  • Or: template class functions should only be implicitly instantiated one at a time if actually used. If a particular template class function isn't used, then it shouldn't be implicitly instantiated - even if other template class functions are used and instantiated.

It seems clear that the second case is the case today because this same pattern is used with PIMPL and unique_ptr with its checked delete, but maybe that was not the case in the past? Was the first case acceptable compiler behavior in the past?

Or in other words, was the compiler buggy, or did it accurately follow the C++98 standard, and the standard changed over the years?

(Fun fact: if you remove the checked delete in C++ Builder, and have function inlining turned off, the project will happily compile. PublicClass.obj will contain a correct ~demo_ptr implementation, and main.obj will contain an incorrect ~demo_ptr implementation with undefined behavior. The function used will depend on the order in which these files are fed to the linker.)

UPDATE: This is due to a compiler bug, as noted by Andy Prowl, which is still not fixed in C++ Builder XE8. I've reported the bug to Embarcadero: bcc32 compiler causes undefined behavior when using std::auto_ptr with PIMPL idiom because template instantiation rules do not follow C++ spec

James Johnston
  • 9,264
  • 9
  • 48
  • 76
  • BTW `demo_ptr` violates the rule of 3 -- you'll want to fix that before it bites you with double deletion. – Ben Voigt Apr 09 '13 at 00:21
  • "Old compiler" and the symptoms you've stated reakes of: likely a compiler bug. To my knowledge there is not a single C++ compiler that is fully compliant with the language spec. The last version of clang is probably your best bet, but understand if you're using a really old compiler, you can expect really old and buggy behavior. – Nathan Ernst Apr 09 '13 at 01:07
  • @BenVoigt: understood; this is just a pedantic test case shortened for brevity. In reality I just use auto_ptr. – James Johnston Apr 09 '13 at 01:39
  • Why do you use `auto_ptr`? – Yakk - Adam Nevraumont Apr 09 '13 at 03:07
  • @Yakk: The code already widely uses it, and the runtime library doesn't yet support unique_ptr. We'll switch someday. – James Johnston Apr 09 '13 at 14:15
  • @ildjarn: So that the compiler doesn't autogenerate a destructor that has undefined behavior, which would happen on any C++ compiler if you left this out. – James Johnston Apr 22 '13 at 20:11
  • @ildjarn: Really? Have you even tried implementing the PIMPL pattern with a smart pointer? `unique_ptr` will throw a compile error if you use it wrong, but other smart pointer classes such as `auto_ptr` will happily and silently compile code with undefined behavior. Why not tell Herb Sutter to start over with a good book? http://herbsutter.com/gotw/_100/ read item #2. – James Johnston Apr 22 '13 at 22:28
  • @James : And what does that have to do with an empty/pointless non-trivial destructor? – ildjarn Apr 22 '13 at 22:34
  • @ildjarn: Quoting link, since you didn't bother to read it: "You still need to write the visible class’ destructor yourself and define it out of line in the implementation file, even if ... it’s the same as what the compiler would generate ... unique_ptr’s destructor requires a complete type in order to invoke delete ... By writing it yourself in the implementation file, you force it to be defined in a place where impl is already defined, and this successfully prevents the compiler from trying to automatically generate the destructor on demand in the caller’s code where impl is not defined." – James Johnston Apr 22 '13 at 22:40

2 Answers2

5

If a template class is used, then all member functions of the class should be implicitly instantiated - whether used or not?

No, this is definitely not the case. Per Paragraph 14.7.1/10 of the C++11 Standard (and Paragraph 14.7.1/9 of the C++03 Standard) very clearly specifies:

An implementation shall not implicitly instantiate a function template, a member template, a non-virtual member function, a member class, or a static data member of a class template that does not require instantiation.

As for when instantiation is required, Paragraph 14.7.1/2 specifies:

Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; [...]

This is certainly not the case if a member function is never referenced.


Unfortunately, I can't provide an official reference on what the rules were before C++03, but to the best of my knowledge the same "lazy" instantiation mechanism was adopted in C++98.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • The member function certainly is referenced, from the automatically-generated portions of `PublicClass::~PublicClass` as it invokes member destructors such as `demo_ptr::~demo_ptr`. – Ben Voigt Apr 09 '13 at 00:15
  • 1
    @BenVoigt: I did not say the member function is not referenced. I merely answered the question about what the Standard mandates: *"This is certainly not the case **if a member function is never referenced**."* – Andy Prowl Apr 09 '13 at 00:17
  • Ok, glad you understand. I still think the clarification is useful for other readers. – Ben Voigt Apr 09 '13 at 00:19
  • Exactly the answer I was looking for with quotations from the standard. Thank you. – James Johnston Apr 09 '13 at 14:14
1

unique_ptr is a funny beast.

Unlike auto_ptr, which called the destructor of the referenced object from the smart pointer destructor, unique_ptr::~unique_ptr merely invokes a deleter function which has been previously stored.

The deleter function is stored in the unique_ptr constructor, which is called for each and every PublicClass constructor. The user-defined constructor is defined in a context where PrivateClass::~PrivateClass is available, so that's ok. But what about other implicitly-generated PublicClass constructors, such as a move constructor? They're generated at the point of use; they also need to initialize the unique_ptr member, meaning they must supply a deleter. But without PrivateClass::~PrivateClass, they can't.

Wait, your question mentions unique_ptr, but your code isn't using it. Weird...

Even when the destructor is invoked from the smart pointer destructor, it can still be ODR-used from the containing class constructors. This is for exception-safety -- the constructor needs the capability to tear down a partially-constructed class, which includes destruction of members.

It looks like C++Builder may be generating a PublicClass copy or more constructor, even though your program doesn't use it. That doesn't fall afoul of the rule Andy mentioned, because PublicClass isn't a template. I think the compiler is entitled to generate a defaulted copy constructor for PublicClass when it processes the class definition, since you can't provide an out-of-class definition for a defaulted member. Respecting the rule of three for demo_ptr will preclude PublicClass having a copy constructor, and therefore may solve your problem.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720