How Does =Delete on Destructor Prevent Stack Allocation

How does =delete on destructor prevent stack allocation?

The destructor of a variable with automatic storage duration (i.e. a local variable) would need to run when the variable's lifetime ends. If there is no accessible destructor the compiler refuses to compile the code that allocates such a variable.

Basically the distinction between "stack allocation" (an inaccurate choice of term by the way) and free store allocation is that with local variables constructor/destructor calls always come in pairs, whereas with free store allocation you can construct an object without ever destructing it. Therefore by preventing access to the destructor your code makes it impossible to allocate a local variable of the type (if the constructor runs the destructor must also run, but there is no destructor so the program is rejected).

Uses of destructor = delete;

If you have an object which should never, ever be deleted or stored on the stack (automatic storage), or stored as part of another object, =delete will prevent all of these.

struct Handle {
~Handle()=delete;
};

struct Data {
std::array<char,1024> buffer;
};

struct Bundle: Handle {
Data data;
};

using bundle_storage = std::aligned_storage_t<sizeof(Bundle), alignof(Bundle)>;

std::size_t bundle_count = 0;
std::array< bundle_storage, 1000 > global_bundles;

Handle* get_bundle() {
return new ((void*)global_bundles[bundle_count++]) Bundle();
}
void return_bundle( Handle* h ) {
Assert( h == (void*)global_bundles[bundle_count-1] );
--bundle_count;
}
char get_char( Handle const* h, std::size_t i ) {
return static_cast<Bundle*>(h).data[i];
}
void set_char( Handle const* h, std::size_t i, char c ) {
static_cast<Bundle*>(h).data[i] = c;
}

Here we have opaque Handles which may not be declared on the stack nor dynamically allocated. We have a system to get them from a known array.

I believe nothing above is undefined behavior; failing to destroy a Bundle is acceptable, as is creating a new one in its place.

And the interface doesn't have to expose how Bundle works. Just an opaque Handle.

Now this technique can be useful if other parts of the code need to know that all Handles are in that specific buffer, or their lifetime is tracked in specific ways. Possibly this could also be handled with private constructors and friend factory functions.

Will destructor delete built-in types and pointer objects?

What the spec means is that no code is run to clean up int i. It simply ceases to exist. Its memory is part of Foo and whenever a Foo instance is released, then i goes with it.

For pointers the same is true, the pointer itself will simply disappear (it's really just another number), but the pointer might point at something that needs to also be released. The compiler doesn't know if that is the case, so you have to write a destructor.

This is why things like std::shared_ptr exist; they are clever pointers (aka 'smart') and the compiler does know if it points at something that needs to be released and will generate the correct code to do so. This is why you should always use smart pointers instead of 'naked' ones (like int *p).

Using a union to prevent destruction?

[class.dtor]/15 says that a program is ill-formed if a potentially invoked destructor is defined as deleted.

[class.base.init]/12 says that a destructor of a potentially constructed subobject of a class is potentially invoked in a non-delegating constructor.

[special]/7 says that all non-static data members are potentially constructed subobjects, although the sentence is qualified a bit weirdly I think ("For a class, [...]").

I don't see any exceptions in this for single-member classes, unions or noexcept. So it seems to me that GCC is correct for S1.

Clang is also the only compiler out of Clang, GCC, ICC and MSVC that doesn't reject this version.

For S0 however, this reasoning would also apply, making it ill-formed as well. However none of the four compilers agrees on that, so maybe I am wrong.


The quoted wording comes mostly from CWG issue 1424. Clang lists its implementation status currently as unknown (https://clang.llvm.org/cxx_dr_status.html), as does GCC (https://gcc.gnu.org/projects/cxx-dr-status.html).

Does the synthesized destructor destroy the memory allocated on the heap?

Any memory we allocate on the heap using new must always be freed by using the keyword delete.

So,you have to explicitly free the memory allocated by new on the heap using the keyword delete as you did in the destructor. The synthesized destructor will not do it for you.

Note if you don't want to deal with memory management by yourself then you can use smart pointers. That way you will not have to use delete explicitly by yourself because the destructor corresponding to the smart pointer will take care of freeing up the memory. This essentially means if the data member named p was a smart pointer instead of a normal(built in) pointer, then you don't have to write delete p in the destructor of your class Foo.

C++ - Does the Destructor just deallocate the memory or does it actually delete the object?

In C++ parlance, two separate concepts are 'object lifetime' and 'storage duration'. A duration is pretty much the same as lifetime, but Standard uses two distinct words here. Storage refers to the physical memory occupied by the object, while object lifetime refers to the time when it is allowed to access the object.

With this in mind, one has two understand that constructor and destructor determine object lifetime - it is not allowed to access the object before constructor was called and after destructor was called, and do not say anything about storage lifetime. It is obvious that storage duration (or lifetime) should not be smaller than object lifetime - once can not access object that has no physical representation - but can be longer than it.

It is easily seen when placement new is used together with explicit destructor call - storage remains available, but the object has gone.



Related Topics



Leave a reply



Submit