Strange enumeration in the destructor

I am currently reading Protocol Buffer source code and I found one weird enum code defined here

  ~scoped_ptr() { enum { type_must_be_complete = sizeof(C) }; delete ptr_; } void reset(C* p = NULL) { if (p != ptr_) { enum { type_must_be_complete = sizeof(C) }; delete ptr_; ptr_ = p; } } 

Why is enum { type_must_be_complete = sizeof(C) }; specified here enum { type_must_be_complete = sizeof(C) }; ? what is it used for?

+83
c ++ enums
Nov 19 '15 at 10:09
source share
4 answers

This trick avoids UB by ensuring that the C definition is available when compiling this destructor. Otherwise, compilation will fail because the incomplete sizeof (forwarded declared types) cannot be defined, but pointers can be used.

In compiled binary, this code will be optimized and will have no effect.

Note: Removing an incomplete type may have undefined behavior from 5.3.5 / 5 :.

if the deleted object is of type incomplete class at the point of deletion, and the full class has a nontrivial destructor or deallocation, the behavior is undefined .

g++ even throws the following warning:

warning: possible problem detected by calling delete statement:
warning: 'p' has an invalid type
warning: forward declaration of 'struct C'

+80
Nov 19 '15 at 10:12
source share

sizeof(C) will fail at compile time if C not a full type. Setting the local enum region into it makes the statement benign at runtime.

This is the programmer's way of protecting himself from himself: the behavior of the subsequent delete ptr_ on an incomplete type undefined if it has a nontrivial destructor.

+31
Nov 19 '15 at 10:11
source share

To understand enum , start by looking at the destructor without it:

 ~scoped_ptr() { delete ptr_; } 

where ptr_ is C* . If type C is incomplete at this point, then all the compiler knows is struct C; , then (1) the default do-nothing destructor created is used for the specified instance of C. This is unlikely to be correct for an object controlled by a smart pointer.

If deleting with a pointer to an incomplete type always had an Undefined Behavior, then the standard could simply require the compiler to diagnose it and complete the failure. But it is clearly defined when the real destructor is trivial: the knowledge that a programmer can have, but the compiler does not have. Why does the language define and allow this above me, but C ++ supports many practices that today are not considered as best practices.

A full type has a known size, and therefore sizeof(C) will compile if and only if C is a full type - with a known destructor. Therefore, it can be used as a guard. One way is just

 (void) sizeof(C); // Type must be complete 

I would suggest that with some compilers and parameters, the compiler optimizes it before it can notice that it should not compile and that enum is a way to avoid such inappropriate compiler behavior:

 enum { type_must_be_complete = sizeof(C) }; 

An alternative explanation for choosing enum , rather than just a dropped expression, is simply personal preference.

Or as James T. Hagget suggests in a comment on this answer: "Enumeration can be a way to create a pseudo-portable error message at compile time."




(1) The default do-nothing destructor for an incomplete type was a problem with the old std::auto_ptr . It was so insidious that he crawled into the GOTW element about the PIMPL idiom written by the chairman of the international standardization committee C ++ Herb Sutter. Of course, std::auto_ptr deprecated now, some other mechanism will be used instead.

+28
Nov 19. '15 at 10:24
source share

Maybe a trick to make sure that C defined.

+3
Nov 19 '15 at 10:12
source share



All Articles