17

Comeau, g++ (ideone) and EDG accept the following code without diagnostic. Visual C++ compiles successfully, albeit with warning C4624.

class indestructible_base
{
  ~indestructible_base();
};

class T : indestructible_base
{
public:
  //T() {}
};

int main(void) { new T(); }

Uncomment the constructor and it no longer compiles.

Perhaps it's the rule that if an exception occurs inside the constructor, subobjects must be destroyed? Seems odd, since the body is empty and can't cause an exception. Even so, add an exception-specification vouching for the fact that no exception will be thrown (throw() or noexcept) and it makes no difference.

Why does a user-declared constructor require access to the base class destructor, while an automatically-generated constructor does not?

This question was inspired by: Preventing a Destructor from Running in C++

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 4
    FWIW, Clang rejects the program in both cases in C++0x mode, but behaves like g++ in c++98 modes. – justin Feb 03 '12 at 03:53
  • when you declare ~indestructible_base() as public, the compiler does not complain. – haberdar Feb 03 '12 at 04:05
  • 1
    This may have to do with exceptions. If there is a user-defined constructor, there is no guarantee that it won't throw. If it throws, the base subobject needs to be destroyed. But I may be wrong. - It also won't compile even without the user-defined constructor if you have a non-trivial member, such as std::string. (Again, that could throw in the compiler-generated constructor.) – visitor Feb 03 '12 at 10:12
  • definitely the wrong tag: accessibility this is not. – Norman B. Robins0n Feb 03 '12 at 15:44
  • @Norman: Sorry about that, the correct tag was `access-modifiers`. – Ben Voigt Feb 03 '12 at 16:18
  • @visitor: I agree, and I did notice the behavior with a `std::string` member also (some compilers cough, VC++ 8, cough still get it wrong, but most reject it). But I don't agree with your sentence "If there is a user-defined constructor, there is no guarantee that it won't throw". If the constructor is marked `throw()` or `noexcept`, then there is such a guarantee -- any attempt to throw will cause the constructor to be terminated by `std::unexpected`, not by an exception. – Ben Voigt Feb 03 '12 at 16:22
  • I think the exception idea is clever, but a red herring. If the implementation is not given here, then there will be no code generated to call the destructor in this translation unit. Where it is defined, however, THAT should yield an access error. – spraff Feb 03 '12 at 16:38
  • @spraff: But what could cause the user-defined constructor to generate a call to the (deleted) destructor and fail compilation, if not exception-handling? It certainly doesn't fall under any of the other implicit destructor calls listed in section 12.4. – Ben Voigt Feb 03 '12 at 16:52

2 Answers2

1

I suspect that this might be compiler-specific behavior. Here's my theory:

Because (in this particular case) an implicitly-defined T() is a trivial constructor (as defined in 12.1(5) of the standard), the compiler doesn't even attempt to generate a body for T(). Since there's no ctor body, there are no exceptions that could possibly be generated during "construction" (of which there isn't any, really), so there's no need to generate a dtor call, and so no need to generate a dtor body, only to find out that the base class's dtor is private.

But as soon as T() becomes non-trivial (even if it remains implicitly-defined), a ctor body must be generated, and you get the error. Something as simple as adding a member to class T that has a user-defined constructor would make the implicitly-defined T() become non-trivial.

A separate, but related, issue is that new T() doesn't generate a dtor call (since you don't have a corresponding delete anywhere). In contrast, if I just replace new T() with T dummy in your code, then I get the following from gcc, suggesting that it's now doing the full check for dtor accessibility (as a consequence of having to generate a dtor call):

test.cpp: In destructor 'T::~T()':
test.cpp:3: error: 'indestructible_base::~indestructible_base()' is private
test.cpp:7: error: within this context
test.cpp: In function 'int main()':
test.cpp:12: note: synthesized method 'T::~T()' first required here
test.cpp:12: warning: unused variable 'dummy'
jjlin
  • 4,462
  • 1
  • 30
  • 23
  • Right. But a `noexcept` user-defined constructor also can't throw, so why does that fail? – Ben Voigt Feb 03 '12 at 19:22
  • @BenVoigt. It seems jjlin is correct. Look again at 12.1.5. Seems to not have anything to do with exceptions, but instead if the constructor is 'trivial' or not. For a constructor to be trivial all the members and bases must have trivial constructors (+ other requirements). (indestructible_base could as well be a member.) No destructor is generated in case of a trivial constructor. Also, declaring a constructor makes it non-trivial (except when using = default in c++11). The code is fine because nobody called that (non existing) destructor. Also note the comment jjlin makes about delete; – Johan Lundberg Feb 06 '12 at 23:30
  • Looking through the gcc source, I get the impression that whether the ctor throws or not isn't checked. Of course, the source is pretty complex, so I can't say for sure, but it may simply be that the compiler writers just take the conservative approach and generate a dtor if they have to generate a ctor. – jjlin Feb 07 '12 at 06:42
0

Well, if the automatically-generated constructor calls a possibly-throwing constructor, then it will give the same access error.

#include <string>

class indestructible_base
{
  ~indestructible_base();
  std::string s; // <------ this may throw
};

class T : indestructible_base
{
public:
  //T() {}
};

int main(void) { new T(); }

So I guess exceptions is the answer. In ANSI ISO IEC 14882 the only noexcept(true) string constructor is the move constructor. I believe this should compile but ideone says no.

spraff
  • 32,570
  • 22
  • 121
  • 229
  • But if a defaulted non-throwing constructor is permitted, should not an explicit `throw()` or `noexcept` constructor also be permitted? – Ben Voigt Feb 03 '12 at 17:15
  • I expect [this](http://ideone.com/T4rnz) should compile (but not link) fine, although ideone doesn't like the "noexcept" and I don't have a C++11 compiler to hand. – spraff Feb 03 '12 at 17:21
  • FWIW, Microsoft DevStudio v10 and Microsoft DevStudio v11 dev preview can compile this code with or without the T() being defined. – Vorlauf Feb 03 '12 at 18:04