124

Basic Question: when does a program call a class' destructor method in C++? I have been told that it is called whenever an object goes out of scope or is subjected to a delete

More specific questions:

1) If the object is created via a pointer and that pointer is later deleted or given a new address to point to, does the object that it was pointing to call its destructor (assuming nothing else is pointing to it)?

2) Following up on question 1, what defines when an object goes out of scope (not regarding to when an object leaves a given {block}). So, in other words, when is a destructor called on an object in a linked list?

3) Would you ever want to call a destructor manually?

Pat Murray
  • 3,545
  • 6
  • 23
  • 32
  • 3
    Even your specific questions are too broad. "That pointer is later deleted" and "given a new address to point to" are quite different. Search more (some of these have been answered), and then ask separate questions for the parts you couldn't find. – Matthew Flaschen Apr 10 '12 at 00:14
  • @MatthewFlaschen can you please give please link of those question have been answered , I searched but unable to find them. – Abhishek Mane May 13 '21 at 12:24

10 Answers10

77

1) If the object is created via a pointer and that pointer is later deleted or given a new address to point to, does the object that it was pointing to call its destructor (assuming nothing else is pointing to it)?

It depends on the type of pointers. For example, smart pointers often delete their objects when they are deleted. Ordinary pointers do not. The same is true when a pointer is made to point to a different object. Some smart pointers will destroy the old object, or will destroy it if it has no more references. Ordinary pointers have no such smarts. They just hold an address and allow you to perform operations on the objects they point to by specifically doing so.

2) Following up on question 1, what defines when an object goes out of scope (not regarding to when an object leaves a given {block}). So, in other words, when is a destructor called on an object in a linked list?

That's up to the implementation of the linked list. Typical collections destroy all their contained objects when they are destroyed.

So, a linked list of pointers would typically destroy the pointers but not the objects they point to. (Which may be correct. They may be references by other pointers.) A linked list specifically designed to contain pointers, however, might delete the objects on its own destruction.

A linked list of smart pointers could automatically delete the objects when the pointers are deleted, or do so if they had no more references. It's all up to you to pick the pieces that do what you want.

3) Would you ever want to call a destructor manually?

Sure. One example would be if you want to replace an object with another object of the same type but don't want to free memory just to allocate it again. You can destroy the old object in place and construct a new one in place. (However, generally this is a bad idea.)

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}
David Schwartz
  • 166,415
  • 16
  • 184
  • 259
  • 2
    I thought the last of your examples declared a function? It's an example of the "most vexing parse". (The other more trivial point is that I guess you meant `new Foo()` with a capital 'F'.) – Stuart Golodetz Apr 10 '12 at 00:21
  • 1
    I think `Foo myfoo("foo")` is not Most Vexing Parse, but `char * foo = "foo"; Foo myfoo(foo);` is. – Cosine May 13 '14 at 00:58
  • It may be a stupid question, but shouldn't the `delete myFoo` be called before `Foo *myFoo = new Foo("foo");`? Or else you would be delete the newly created object, no? – Matheus Rocha Feb 05 '16 at 13:50
  • There is no `myFoo` before the `Foo *myFoo = new Foo("foo");` line. That line creates a brand new variable called `myFoo`, shadowing any existing one. Though in this case, there is no existing one since the `myFoo` above is in the scope of the `if`, which has ended. – David Schwartz Feb 05 '16 at 17:37
  • Note that in the case of `Foo myFoo("foo");` the destructor will be called when `myFoo` goes out of scope, after the closing brace of the `if` statement. – Warlike Chimpanzee Apr 25 '17 at 15:41
  • you can use only { and } to start a new scope, you can remove the if(1), works the same in many languages – aeroson Jun 14 '17 at 12:36
  • 1
    @galactikuh A "smart pointer" is something that acts like a pointer to an object but that also has features that make it easier to manage the lifetime of that object. – David Schwartz Dec 15 '17 at 17:35
  • @DavidSchwartz how to know which is `smart ptr` or which one is `normal ptr` ? – Abhishek Mane May 13 '21 at 10:50
23

Others have already addressed the other issues, so I'll just look at one point: do you ever want to manually delete an object.

The answer is yes. @DavidSchwartz gave one example, but it's a fairly unusual one. I'll give an example that's under the hood of what a lot of C++ programmers use all the time: std::vector (and std::deque, though it's not used quite as much).

As most people know, std::vector will allocate a larger block of memory when/if you add more items than its current allocation can hold. When it does this, however, it has a block of memory that's capable of holding more objects than are currently in the vector.

To manage that, what vector does under the covers is allocate raw memory via the Allocator object (which, unless you specify otherwise, means it uses ::operator new). Then, when you use (for example) push_back to add an item to the vector, internally the vector uses a placement new to create an item in the (previously) unused part of its memory space.

Now, what happens when/if you erase an item from the vector? It can't just use delete -- that would release its entire block of memory; it needs to destroy one object in that memory without destroying any others, or releasing any of the block of memory it controls (for example, if you erase 5 items from a vector, then immediately push_back 5 more items, it's guaranteed that the vector will not reallocate memory when you do so.

To do that, the vector directly destroys the objects in the memory by explicitly calling the destructor, not by using delete.

If, perchance, somebody else were to write a container using contiguous storage roughly like a vector does (or some variant of that, like std::deque really does), you'd almost certainly want to use the same technique.

Just for example, let's consider how you might write code for a circular ring-buffer.

#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC

template <class T>
class circular_buffer {
    T *data;
    unsigned read_pos;
    unsigned write_pos;
    unsigned in_use;
    const unsigned capacity;
public:
    circular_buffer(unsigned size) :
        data((T *)operator new(size * sizeof(T))),
        read_pos(0),
        write_pos(0),
        in_use(0),
        capacity(size)
    {}

    void push(T const &t) {
        // ensure there's room in buffer:
        if (in_use == capacity) 
            pop();

        // construct copy of object in-place into buffer
        new(&data[write_pos++]) T(t);
        // keep pointer in bounds.
        write_pos %= capacity;
        ++in_use;
    }

    // return oldest object in queue:
    T front() {
        return data[read_pos];
    }

    // remove oldest object from queue:
    void pop() { 
        // destroy the object:
        data[read_pos++].~T();

        // keep pointer in bounds.
        read_pos %= capacity;
        --in_use;
    }
  
~circular_buffer() {
    // first destroy any content
    while (in_use != 0)
        pop();

    // then release the buffer.
    operator delete(data); 
}

};

#endif

Unlike the standard containers, this uses operator new and operator delete directly. For real use, you probably do want to use an allocator class, but for the moment it would do more to distract than contribute (IMO, anyway).

Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
10
  1. When you create an object with new, you are responsible for calling delete. When you create an object with make_shared, the resulting shared_ptr is responsible for keeping count and calling delete when the use count goes to zero.
  2. Going out of scope does mean leaving a block. This is when the destructor is called, assuming that the object was not allocated with new (i.e. it is a stack object).
  3. About the only time when you need to call a destructor explicitly is when you allocate the object with a placement new.
Sergey Kalinichenko
  • 675,664
  • 71
  • 998
  • 1,399
6

1) Objects are not created 'via pointers'. There is a pointer that is assigned to any object you 'new'. Assuming this is what you mean, if you call 'delete' on the pointer, it will actually delete (and call the destructor on) the object the pointer dereferences. If you assign the pointer to another object there will be a memory leak; nothing in C++ will collect your garbage for you.

2) These are two separate questions. A variable goes out of scope when the stack frame it's declared in is popped off the stack. Usually this is when you leave a block. Objects in a heap never go out of scope, though their pointers on the stack may. Nothing in particular guarantees that a destructor of an object in a linked list will be called.

3) Not really. There may be Deep Magic that would suggest otherwise, but typically you want to match up your 'new' keywords with your 'delete' keywords, and put everything in your destructor necessary to make sure it properly cleans itself up. If you don't do this, be sure to comment the destructor with specific instructions to anyone using the class on how they should clean up that object's resources manually.

Nathaniel Ford
  • 16,853
  • 18
  • 74
  • 88
3
  1. Pointers -- Regular pointers don't support RAII. Without an explicit delete, there will be garbage. Fortunately C++ has auto pointers that handle this for you!

  2. Scope -- Think of when a variable becomes invisible to your program. Usually this is at the end of {block}, as you point out.

  3. Manual destruction -- Never attempt this. Just let scope and RAII do the magic for you.

chrisaycock
  • 32,202
  • 12
  • 79
  • 116
  • A note: auto_ptr is deprecated, as your link mentions. – tnecniv Apr 10 '12 at 00:18
  • `std::auto_ptr` is deprecated in C++11, yes. If the OP actually has C++11, he should use `std::unique_ptr` for single owners, or `std::shared_ptr` for reference-counted multiple owners. – chrisaycock Apr 10 '12 at 00:22
  • 'Manual destruction -- Never attempt this'. I very often queue off object pointers to a different thread using a system call that the compiler does not understand. 'Relying' on scope/auto/smart pointers would cause my apps to fail catastrophically as objects were deleted by the calling thread before they could be handled by the consumer thread. This issue affects scope-limited and refCounted objects and interfaces. Only pointers and explicit delete will do. – Martin James Apr 10 '12 at 00:27
  • @MartinJames Can you post an example of a system call that the compiler does not understand? And how are you implementing the queue? Not `std::queue?` I've found that `pipe()` between a producer and consumer thread make concurrency a lot easier, if the copying isn't too expensive. – chrisaycock Apr 10 '12 at 00:30
  • myObject=new myClass(); PostMessage(aHandle,WM_APP,0,LPPARAM(myObject)); – Martin James Apr 10 '12 at 00:34
  • ..or PostMessage(Handle,WM_APP,0,long(myObject)); The type of the wParam and lParam varies between environments/languages :( – Martin James Apr 10 '12 at 00:41
  • I would only use a pipe for inter-process comms, and then, only for small data items. For threads in the same process, using a mechanism that copies data fails to take advantage of the shared memory space within the process. TBH, I'm wary of templated vectors, deques etc. as well - they seem to actively encourage developers to repeatedly call copy constructors for huge stack objects, just to avoid using a pointer or reference. – Martin James Apr 10 '12 at 00:49
3

To give a detailed answer to question 3: yes, there are (rare) occasions when you might call the destructor explicitly, in particular as the counterpart to a placement new, as dasblinkenlight observes.

To give a concrete example of this:

#include <iostream>
#include <new>

struct Foo
{
    Foo(int i_) : i(i_) {}
    int i;
};

int main()
{
    // Allocate a chunk of memory large enough to hold 5 Foo objects.
    int n = 5;
    char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));

    // Use placement new to construct Foo instances at the right places in the chunk.
    for(int i=0; i<n; ++i)
    {
        new (chunk + i*sizeof(Foo)) Foo(i);
    }

    // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
    for(int i=0; i<n; ++i)
    {
        Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
        std::cout << foo->i << '\n';
        foo->~Foo();
    }

    // Deallocate the original chunk of memory.
    ::operator delete(chunk);

    return 0;
}

The purpose of this kind of thing is to decouple memory allocation from object construction.

Stuart Golodetz
  • 19,132
  • 3
  • 45
  • 79
1

Whenever you use "new", that is, attach an address to a pointer, or to say, you claim space on the heap, you need to "delete" it.
1.yes, when you delete something, the destructor is called.
2.When the destructor of the linked list is called, it's objects' destructor is called. But if they are pointers, you need to delete them manually. 3.when the space is claimed by "new".

cloudygoose
  • 488
  • 5
  • 16
1

Remember that Constructor of an object is called immediately after the memory is allocated for that object and whereas the destructor is called just before deallocating the memory of that object.

0

Yes, a destructor (a.k.a. dtor) is called when an object goes out of scope if it is on the stack or when you call delete on a pointer to an object.

  1. If the pointer is deleted via delete then the dtor will be called. If you reassign the pointer without calling delete first, you will get a memory leak because the object still exists in memory somewhere. In the latter instance, the dtor is not called.

  2. A good linked list implementation will call the dtor of all objects in the list when the list is being destroyed (because you either called some method to destory it or it went out of scope itself). This is implementation dependent.

  3. I doubt it, but I wouldn't be surprised if there is some odd circumstance out there.

tnecniv
  • 557
  • 2
  • 5
  • 11
  • 1
    "If you reassign the pointer without calling delete first, you will get a memory leak because the object still exists in memory somewhere.". Not necessarily. It could have been deleted through another pointer. – Matthew Flaschen Apr 10 '12 at 00:18
0

If the object is created not via a pointer(for example,A a1 = A();),the destructor is called when the object is destructed, always when the function where the object lies is finished.for example:

void func()
{
...
A a1 = A();
...
}//finish


the destructor is called when code is execused to line "finish".

If the object is created via a pointer(for example,A * a2 = new A();),the destructor is called when the pointer is deleted(delete a2;).If the point is not deleted by user explictly or given a new address before deleting it, the memory leak is occured. That is a bug.

In a linked list, if we use std::list<>, we needn't care about the desctructor or memory leak because std::list<> has finished all of these for us. In a linked list written by ourselves, we should write the desctructor and delete the pointer explictly.Otherwise, it will cause memory leak.

We rarely call a destructor manually. It is a function providing for the system.

Sorry for my poor English!

wyx
  • 59
  • 1
  • 7
  • It's not true that you can't call a destructor manually - you can (see the code in my answer, for example). What is true is that the vast majority of the time you shouldn't :) – Stuart Golodetz Apr 10 '12 at 00:37