views:

529

answers:

3

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 !
};
+6  A: 

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.

AndreyT
+3  A: 

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
"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
@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
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
+13  A: 

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
I wonder: why do they use a reference to T* ?
Matthieu M.
That's a typo. Fixed.
Kirill V. Lyadvinsky
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
In C size of empty struct is equal to 0.
Kirill V. Lyadvinsky
@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).
AndreyT
@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.
AndreyT
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.
AndreyT
@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.
AndreyT
@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
@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.
AndreyT
`struct X{};` is empty.
Kirill V. Lyadvinsky
@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.
AndreyT
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