How do I call the class's destructor?
Asked Answered
Y

8

35

I have a simple C++ code, but I don't know how to use the destructor:

class date {

public:
    int day;
    date(int m)
    {
        day =m;
    }

    ~date(){
    cout << "I wish you have entered the year \n" << day;    
    }
};


int main()
{
  date ob2(12);
  ob2.~date();
  cout << ob2.day;
  return 0;
}

The question that I have is, what should I write in my destructor code, that after calling the destructor, it will delete the day variable?

Yetac answered 27/10, 2010 at 18:22 Comment(2)
A lot of answers reference "the stack." If you aren't sure what the stack is, read this page: web.archive.org/web/20071029040931/www.dirac.org/linux/gdb/…Gonium
Would you specify "delete"? Do you mean variable "day" memory address available to other variable, or data in the memory address is erased?Dvina
O
19

You should not call your destructor explicitly.

When you create your object on the stack (like you did) all you need is:

int main()
{
  date ob2(12);
  // ob2.day holds 12
  return 0; // ob2's destructor will get called here, after which it's memory is freed
}

When you create your object on the heap, you kinda need to delete your class before its destructor is called and memory is freed:

int main()
{
  date* ob2 = new date(12);
  // ob2->day holds 12
  delete ob2; // ob2's destructor will get called here, after which it's memory is freed
  return 0;   // ob2 is invalid at this point.
}

(Failing to call delete on this last example will result in memory loss.)

Both ways have their advantages and disadvantages. The stack way is VERY fast with allocating the memory the object will occupy and you do not need to explicitly delete it, but the stack has limited space and you cannot move those objects around easily, fast and cleanly.

The heap is the preferred way of doing it, but when it comes to performance it is slow to allocate and you have to deal with pointers. But you have much more flexibility with what you do with your object, it's way faster to work with pointers further and you have more control over the object's lifetime.

Ossicle answered 27/10, 2010 at 18:42 Comment(8)
“The heap is the preferred way of doing it” – doing what? Storing objects? Because that would be wrong: the stack is absolutely the preferred way of doing that in C++.Assess
Working with libraries and passing objects between them it is preferable (nay, vital) to have them allocated on the heap. Sure the stack is quick and dirty and in some cases make sense. But when you have objects that you want to use, pass around, move, copy and reference count, then the stack is no place for them. Until C++0x move semantics become commonplace that is...Ossicle
sorry but this is wrong. For argument-passing, use (const) references. For return values – simply copy. Nothing bad will happen. See e.g. cpp-next.com/archive/2009/08/want-speed-pass-by-value Libraries should never require heap-allocated arguments. This would be a very bad library design. And there is nothing quick and dirty about stack allocation.Assess
Exactly all right; I agree fully, but this really does fail to answer the fundamental question the poster had. I believe my answer will give him a better idea of how to 'use' a destructor (since that is the fundamental and simple concept he is lacking) and how it works. It may not be reference-correct and will not stand up to language-lawyers, but I think he will get a better better idea of object lifetime with simple examples. Which is why I think James' post is still ostensibly perfect.Ossicle
if I'm creating objects in a loop, saving each to file, then is the correct way the heap way and call the destructor at the end of the loop each iteration?Graubert
Depends on how you create them in a loop. If you create an object on the stack inside a loop, then you it will get deleted at the end of each loop iteration automatically. If you create it on the heap inside the loop, then you will need to either explicitly delete them at the end of the loop, or store all the pointers and delete them later. (when deleting later, it's usually optimal to deallocate in reverse order to combat fragmentation)Ossicle
This isn't quite right - calling a destructor manually is absolutely required sometimes; the only example I can think off the top of my head is when placement new was called.Pontoon
I think the question is valid, e.g. overwriting std::allocator<> due to additional requirements leads immediately to the question, but you don't answer.Salta
I
30

Rarely do you ever need to call the destructor explicitly. Instead, the destructor is called when an object is destroyed.

For an object like ob2 that is a local variable, it is destroyed when it goes out of scope:

int main() 
{ 
    date ob2(12); 

} // ob2.~date() is called here, automatically!

If you dynamically allocate an object using new, its destructor is called when the object is destroyed using delete. If you have a static object, its destructor is called when the program terminates (if the program terminates normally).

Unless you create something dynamically using new, you don't need to do anything explicit to clean it up (so, for example, when ob2 is destroyed, all of its member variables, including day, are destroyed). If you create something dynamically, you need to ensure it gets destroyed when you are done with it; the best practice is to use what is called a "smart pointer" to ensure this cleanup is handled automatically.

Interstadial answered 27/10, 2010 at 18:27 Comment(0)
O
19

You should not call your destructor explicitly.

When you create your object on the stack (like you did) all you need is:

int main()
{
  date ob2(12);
  // ob2.day holds 12
  return 0; // ob2's destructor will get called here, after which it's memory is freed
}

When you create your object on the heap, you kinda need to delete your class before its destructor is called and memory is freed:

int main()
{
  date* ob2 = new date(12);
  // ob2->day holds 12
  delete ob2; // ob2's destructor will get called here, after which it's memory is freed
  return 0;   // ob2 is invalid at this point.
}

(Failing to call delete on this last example will result in memory loss.)

Both ways have their advantages and disadvantages. The stack way is VERY fast with allocating the memory the object will occupy and you do not need to explicitly delete it, but the stack has limited space and you cannot move those objects around easily, fast and cleanly.

The heap is the preferred way of doing it, but when it comes to performance it is slow to allocate and you have to deal with pointers. But you have much more flexibility with what you do with your object, it's way faster to work with pointers further and you have more control over the object's lifetime.

Ossicle answered 27/10, 2010 at 18:42 Comment(8)
“The heap is the preferred way of doing it” – doing what? Storing objects? Because that would be wrong: the stack is absolutely the preferred way of doing that in C++.Assess
Working with libraries and passing objects between them it is preferable (nay, vital) to have them allocated on the heap. Sure the stack is quick and dirty and in some cases make sense. But when you have objects that you want to use, pass around, move, copy and reference count, then the stack is no place for them. Until C++0x move semantics become commonplace that is...Ossicle
sorry but this is wrong. For argument-passing, use (const) references. For return values – simply copy. Nothing bad will happen. See e.g. cpp-next.com/archive/2009/08/want-speed-pass-by-value Libraries should never require heap-allocated arguments. This would be a very bad library design. And there is nothing quick and dirty about stack allocation.Assess
Exactly all right; I agree fully, but this really does fail to answer the fundamental question the poster had. I believe my answer will give him a better idea of how to 'use' a destructor (since that is the fundamental and simple concept he is lacking) and how it works. It may not be reference-correct and will not stand up to language-lawyers, but I think he will get a better better idea of object lifetime with simple examples. Which is why I think James' post is still ostensibly perfect.Ossicle
if I'm creating objects in a loop, saving each to file, then is the correct way the heap way and call the destructor at the end of the loop each iteration?Graubert
Depends on how you create them in a loop. If you create an object on the stack inside a loop, then you it will get deleted at the end of each loop iteration automatically. If you create it on the heap inside the loop, then you will need to either explicitly delete them at the end of the loop, or store all the pointers and delete them later. (when deleting later, it's usually optimal to deallocate in reverse order to combat fragmentation)Ossicle
This isn't quite right - calling a destructor manually is absolutely required sometimes; the only example I can think off the top of my head is when placement new was called.Pontoon
I think the question is valid, e.g. overwriting std::allocator<> due to additional requirements leads immediately to the question, but you don't answer.Salta
A
18

You do not need to call the destructor explicitly. This is done automatically at the end of the scope of the object ob2, i.e. at the end of the main function.

Furthermore, since the object has automatic storage, its storage doesn’t have to be deleted. This, too, is done automatically at the end of the function.

Calling destructors manually is almost never needed (only in low-level library code) and deleting memory manually is only needed (and only a valid operation) when the memory was previously acquired using new (when you’re working with pointers).

Since manual memory management is prone to leaks, modern C++ code tries not to use new and delete explicitly at all. When it’s really necessary to use new, then a so-called “smart pointer” is used instead of a regular pointer.

Assess answered 27/10, 2010 at 18:26 Comment(0)
I
12

Only in very specific circumstances you need to call the destructor directly. By default the destructor will be called by the system when you create a variable of automatic storage and it falls out of scope or when a an object dynamically allocated with new is destroyed with delete.

struct test {
   test( int value ) : value( value ) {}
   ~test() { std::cout << "~test: " << value << std::endl; }
   int value;
};
int main()
{
   test t(1);
   test *d = new t(2);
   delete d;           // prints: ~test: 2
}                      // prints: ~test: 1 (t falls out of scope)

For completeness, (this should not be used in general) the syntax to call the destructor is similar to a method. After the destructor is run, the memory is no longer an object of that type (should be handled as raw memory):

int main()
{
   test t( 1 );
   t.~test();            // prints: ~test: 1
                         // after this instruction 't' is no longer a 'test' object
   new (&t) test(2);     // recreate a new test object in place
}                        // test falls out of scope, prints: ~test: 2

Note: after calling the destructor on t, that memory location is no longer a test, that is the reason for recreation of the object by means of the placement new.

Innsbruck answered 27/10, 2010 at 18:33 Comment(2)
Usual way of allocating memory to be used with placement new is with malloc (or new with an array). This is the 1st example I see with the stack variable. btw in the 1st example, it would be test *d=new test(2);Aglow
@VJo: I started to write an example with an stack allocated memory buffer, but that required the use of placement new before calling the destructor (small issue) and did not show the potential issue of multiple calls to the destructor. There is also another auto allocated example in the new standard: with the lesser requirements placed on the members of an union, it allows for objects with non-trivial constructor/destructors. In that case, it is up to the user to explicitly call the constructor and destructor of the active member of the union.Epidiascope
B
2

In this case your destructor does not need to delete the day variable.

You only need to call delete on memory that you have allocated with new.

Here's how your code would look if you were using new and delete to trigger invoking the destructor

class date {

  public: int* day; 
  date(int m) { 
      day = new int;
      *day = m; 
  }

  ~date(){ 
      delete day;
      cout << "now the destructor get's called explicitly";
  } 
};

int main() { 
  date *ob2 = new date(12); 
  delete ob2;
  return 0; 
}
Blondell answered 27/10, 2010 at 18:29 Comment(3)
Besides the fact that the code is allocating memory on the heap in order to store an integer, what else is wrong with the style?Blondell
that’s what I meant. But additionally (as I pointed out in my posting below), any usage of new in conjunction with raw pointers is somewhat deprecated in modern C++ because it leads to so much trouble. Certainly for beginners.Assess
The code has the added problem of not having a properly defined copy constructor and copy assignment operator, which is an extremely common pitfall for beginners.Interstadial
J
2

You may be confused by undefined behavior here. The C++ standard has no rules as to what happens if you use an object after its destructor has been run, as that's undefined behavior, and therefore the implementation can do anything it likes. Typically, compiler designers don't do anything special for undefined behavior, and so what happens is an artifact of what other design decisions were made. (This can cause really weird results sometimes.)

Therefore, once you've run the destructor, the compiler has no further obligation regarding that object. If you don't refer to it again, it doesn't matter. If you do refer to it, that's undefined behavior, and from the Standard's point of view the behavior doesn't matter, and since the Standard says nothing most compiler designers will not worry about what the program does.

In this case, the easiest thing to do is to leave the object untouched, since it isn't holding on to resources, and its storage was allocated as part of starting up the function and will not be reclaimed until the function exits. Therefore, the value of the data member will remain the same. The natural thing for the compiler to do when it reads ob2.day is to access the memory location.

Like any other example of undefined behavior, the results could change under any change in circumstances, but in this case they probably won't. It would be nice if compilers would catch more cases of undefined behavior and issue diagnostics, but it isn't possible for compilers to detect all undefined behavior (some occurs at runtime) and often they don't check for behavior they don't think likely.

Jefe answered 27/10, 2010 at 19:34 Comment(0)
G
1

Even though the destructor seems like something you need to call to get rid of or "destroy" your object when you are done using it, you aren't supposed to use it that way.

The destructor is something that is automatically called when your object goes out of scope, that is, when the computer leaves the "curly braces" that you instantiated your object in. In this case, when you leave main(). You don't want to call it yourself.

Gonium answered 27/10, 2010 at 18:55 Comment(0)
G
0

A lot of the answers include " you never do that" but I have a counter example. I haven't performance tested this, indeed there is garbage code left in, but it did run for hours although the overall app bombed eventually with optimization level 3 on g++, so there may be bugs, bit you get the idea here.

mjm_object_pool.h

where a fixed array is created but only some objects are used. You can see explicit destructor calls. These compile and appear to run although I'm still working on best approach. Laugh if you want but it does run and may optimize better. fwiw. In response to the comment below, the relevant code is below. A pointer to a memory block is m_ptr and the objects are typedeffed to "ObjectTy". m_bits is a boolean array for those that were allocated. You can see the details in the code but you can call the dtor without releasing the memory.

void Free()
{
MM_ILOOP(i,m_sz)
{
    if (m_bits[i]) m_ptr[i].~ObjectTy();
}
m_bits.clear();
} // Free 

2nd edit, to answer the OP's question you can call the dtor explicitly if you want to execute it but that is independent of releasing the memory in which the object resides. You could for example do this

Foo foo;
foo.~Foo();
new(&foo)Foo;

or what I finally did,

Foo * p = (Foo*) char[sizeof(Foo)];
new(p)Foo;
p->~Foo();
delete[] p;

how's that? lol

Gnash answered 26/12, 2023 at 17:52 Comment(1)
answers that cannot stand on their own without the external resources are not valid here. All the relevant information should be contained within the post as text or code.Jataka

© 2022 - 2024 — McMap. All rights reserved.