Consider the following class:
struct S { ~S() = delete; };
Shortly and for the purpose of the question: I cannot create instances of S
like S s{};
for I could not destroy them.
As mentioned in the comments, I can still create an instance by doing S *s = new S;
, but I cannot delete it as well.
Therefore, the only use I can see for a deleted destructor is something like this:
struct S { ~S() = delete; static void f() { } }; int main() { S::f(); }
That is, define a class that exposes only a bunch of static functions and forbid any attempt to create an instance of that class.
What are the other uses (if any) of a deleted destructor?
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ).
Whenever a call to destructor is made , the allocated memory to the object is not released but the object is no longer accessible in the program. But delete completely removes the object from memory.
Yes, the destructor will be called for all objects in the array when using delete[] .
Since C++ 11, we can use the keyword delete to declare functions as deleted and thus prohibit the use of these functions in the code. See this article for a brief overview. In particular, the destructor of a class/struct/union can be declared as deleted.
If you have an object which should never, ever be delete
d 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 Handle
s 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.
one scenario could be the prevention of wrong deallocation:
#include <stdlib.h> struct S { ~S() = delete; }; int main() { S* obj= (S*) malloc(sizeof(S)); // correct free(obj); // error delete obj; return 0; }
this is very rudimentary, but applies to any special allocation/deallocation-process (e.g. a factory)
a more 'c++'-style example
struct data { //... }; struct data_protected { ~data_protected() = delete; data d; }; struct data_factory { ~data_factory() { for (data* d : data_container) { // this is safe, because no one can call 'delete' on d delete d; } } data_protected* createData() { data* d = new data(); data_container.push_back(d); return (data_protected*)d; } std::vector<data*> data_container; };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With