Mac
Mac

Reputation: 3559

why does the standard let me free-store allocate classes without destructors?

If you have a class without a destructor:

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

The standard does not let me "locally" allocate an instance of that class:

int main()
{
    A a; //error
}

But it seems like it is ok if I allocate that on free-store:

int main()
{
    a *p = new A();
}

As long as I dont call delete on that pointer:

int main()
{
    a *p = new A();
    delete p; //error
}

So my question is, why does the standard let me have a class without a destructor if I allocate it on free-store? I would guess there are some use cases for that? But what exactly?

Upvotes: 5

Views: 227

Answers (3)

Trevor Hickey
Trevor Hickey

Reputation: 37904

In a multi-threaded environment, you may be sharing the non-destructed class between threads.
If the thread that allocates memory terminates, there is no reason a pointer to that allocated memory couldn't still be in use by another thread.

The standard implies that a constructed object with dynamic storage duration does not qualify for destructor invocation.

12.4 Destructors [class.dtor]

A destructor is invoked implicitly
— for a constructed object with static storage duration at program termination,
— for a constructed object with thread storage duration at thread exit,
— for a constructed object with automatic storage duration when the block in which an object is created exits,
— for a constructed temporary object when its lifetime ends.


We can see the benefits of this through a basic memory sharing example between threads:

#include <thread>
#include <iostream>

//shared object
struct A {

    void say_hello(){ std::cout << ++value << '\n'; }
    ~A() = delete;
    int value;
};

//two threads
void creator();
void user(A* a);

//the creator allocates memory,
//gives it to another thread (via pointer),
//and then ends gracefully.  
//It does not attempt to implicitly call the destructor.
//Nor would we want it to for allocated memory we are sharing.
void creator(){

  A* a = new A();
  a->value = 0;
  std::thread t(user, a);
  t.detach();
}

//The user recieves a pointer to memory,
//and is free to continue using it
//despite the creator thread ending
void user(A* a){
  while(1){
    a->say_hello();
  }
}

//main->creator->user
int main(){
  std::thread t(creator);
  while(1){}
}

Upvotes: 1

Nicol Bolas
Nicol Bolas

Reputation: 474046

So my question is, why does the standard let me have a class without a destructor if I allocate it on free-store?

Because that's not how standard features work.

The = delete syntax you're talking about was invented to solve a number of problems. One of them was very specific: making types that were move-only or immobile, for which the compiler would issue a compile-time error if you attempted to call a copy (or move) constructor/assignment operator.

But the syntax has other purposes when applied generally. By =deleteing a function, you can prevent people from calling specific overloads of a function, mainly to stop certain kinds of problematic implicit conversions. If you don't call a function with a specific type, you get a compile-time error for calling a deleted overload. Therefore, =delete is allowed to be applied to any function.

And the destructor of a class qualifies as "any function".

The designed intent of the feature was not to make types which would be non-destructible. That's simply an outgrowth of permitting =delete on any function. It's not design or intent; it simply is.

While there isn't much use to applying =delete to a destructor, there also isn't much use in having the specification to explicitly forbid its use on a destructor. And there certainly isn't much use in making =delete behave differently when applied to a destructor.

Upvotes: 7

Fantastic Mr Fox
Fantastic Mr Fox

Reputation: 33904

With this:

A a;

You will call the destructor upon exiting the scope (and you have deleted the destructor, hence the error). With this:

A *a = new A();

You simply don't call the destructor (because you never use delete). The memory is cleaned up at the programs completion, but you are essentially guaranteeing a memory leak.

There is no reason for c++ to disallow this behavior because this would create a very specific case to program into a compiler. For example, c++ doesn't disallow this:

int *p; *p = 5;

Even though this is obviously bad, and will always be bad. It is up to the programmer to ensure they don't do this.

There is no reason that you should delete your destructor because it is not a useful behavior.

Upvotes: 5

Related Questions