10

How is that possible that it is allowed to delete object with private destructor in the following code? I've reduced real program to the following sample, but it still compiles and works.

class SomeClass;

int main(int argc, char *argv[])
{
  SomeClass* boo = 0; // in real program it will be valid pointer
  delete boo; // how it can work?

  return -1;
}

class SomeClass
{
private:
  ~SomeClass() {}; // ! private destructor !
};
Sam
  • 7,252
  • 16
  • 46
  • 65
big-z
  • 6,812
  • 5
  • 20
  • 19
  • Interesting..if I move the definition of the class above main() then it throws a compiler error. Otherwise, I just get a `warning C4150: deletion of pointer to incomplete type 'SomeClass'; no destructor called` – Naveen Oct 23 '09 at 07:01
  • 1
    @Naveen: That's expected. Incomplete type is one problem. Private destructor is another. Each has its own diagnostic message. You switch between the two by moving the definition of `SomeClass`. – AnT stands with Russia Oct 24 '09 at 17:22

3 Answers3

15

You are trying to delete object of incomplete class type. C++ Standard says that you'll get undefined behavior in this case (5.3.5/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.

To detect such cases you could use boost::checked_delete:

template<typename T> 
inline void checked_delete( T* p )
{
    typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
    (void) sizeof(type_must_be_complete);
    delete p;
}
Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
  • 1
    This takes advantage of sizeof() never returning 0 even for empty structs/classes, which I'd forgotten. ISO-IEC-14882 section 9.1: "Complete objects and member subobjects of class type shall have nonzero size." The implication is that every object in an array will have a unique memory address. Presumably same for structs -- is that so in C as well? – leander Oct 23 '09 at 10:10
  • In C size of empty struct is equal to 0. – Kirill V. Lyadvinsky Oct 23 '09 at 11:52
  • @leander: Not exactly. You see in C++ it is simply *illegal* to apply `sizeof` to an incomplete type. This is what the above code is trying to catch by `(void) sizeof ...` part. This expression shall stop compilation on a compiler that complies to the standard by issuing an *error*. However, if some strange compiler happens to allow `sizeof` on incomplete types (as an extension) with 0 result, then the `typedef ...` part will catch those and force "an array of negative size" failure. So, you have a main "trap" here (the `(void)` part) and auxiliary trap "just in case" (the `typedef` part). – AnT stands with Russia Oct 24 '09 at 16:29
  • @leander: In C the situation is the same: it is *illegal* to apply `sizeof` to an incomplete type. So, if some C compilers return 0 in this case, it is their own extension that has little to do with C language. – AnT stands with Russia Oct 24 '09 at 16:31
  • I don't know what Kirill means by "in C size of empty struct is equal to 0" and why. Firstly, an *incomplete* struct type is not "an empty struct". Secondly, in C it is impossible to declare an empty struct (i.e. a struct with no members) - it is explicitly illegal. If some C compiler allows it as an extension, the size migtht be 0, but that has little to do with C language. – AnT stands with Russia Oct 24 '09 at 16:40
  • @leander: Oh, I see. It was actually you who brought "empty structs/classes" into the picture... No, it has nothing to do with "empty structs/classes" at all. It is about incomplete types. "Incomplete" and "empty" are two entirely different and absoltely unrelated things. – AnT stands with Russia Oct 24 '09 at 16:54
  • @AndreyT, I was talking about empty structs, not incomplete types. Leander asked: whether `sizeof` for *empty* struct is nonzero. C Standard doesn't guarantees that, but C++ Standard does. – Kirill V. Lyadvinsky Oct 24 '09 at 17:07
  • @Kirill: OK, I already realized *why* you are talking about "empty structs" (see my comment above). But nevertheless, your statement "In C size of empty struct is equal to 0" is incorrect, or misleading at best. There's simply no such thing as "empty struct" in C. That's all I was trying to say. And again (this is to leander), "empty structs" are not relevant here at all. – AnT stands with Russia Oct 24 '09 at 17:17
  • 2
    @Kirill" `struct X {};` is empty and it causes compilation error (or, more formally, "diagnostic message") on any compliant C compiler. Because, as I said above, C does not allow empty structs. – AnT stands with Russia Oct 25 '09 at 21:07
  • I see now, I missed that `struct X{};` is invalid in Standard C. I've compiled it in gcc and it doesn't warned me about an error. – Kirill V. Lyadvinsky Oct 26 '09 at 05:37
8

This code causes undefined behavior (UB). It is UB in C++ to delete an object of incomplete type having a non-trivial destructor. And in your code the type SomeClass is incomplete at the point of delete, and it has a non-trivial destructor. Compilers usually issue a warning about this, since in C++ formally this is not a constraint violation.

So, strictly speaking, your code doesn't "work". It simply compiles and does something undefined when run.

The compiler is just not required to catch this error. The reason for this is that this could be perfectly fine if your object has a trivial destructor. The compiler has no way of knowing what kind of destructor this type will eventually have, so it can't say for sure whether this is an error or not.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
4

Because SomeClass type is not completely declared when invoking operator delete.

Deleting such a pointer is undefined behavior, but in practice most compilers would just free the memory (if the pointer was non-NULL) and not call the destructor.

For example, g++ will give you a warning about this issue:

foo.cpp: In function 'int main(int, char**)':
foo.cpp:6: warning: possible problem detected in invocation of delete operator:
foo.cpp:5: warning: 'boo' has incomplete type
foo.cpp:1: warning: forward declaration of 'struct SomeClass'
foo.cpp:6: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.
laalto
  • 150,114
  • 66
  • 286
  • 303
  • 1
    "Deleting such a pointer just would just free the memory..." - NO! The behaviour is *UNDEFINED*, i.e. any assumptions on what "essentially" happens are invalid. – DevSolar Oct 23 '09 at 09:56
  • 1
    @DevSolar: True, it's undefined as per standard. In practice that's what will happen on most compilers. I've updated the answer to reflect this. – laalto Oct 23 '09 at 10:09
  • I have somewhat of a problem with relying on anything outside the well-behavioured realm. Especially in code of co-workers. It drives me up the wall. I blame answers like yours. ( ;-) <<-- important smiley) – DevSolar Oct 23 '09 at 10:47