14

This seems like a rather trivial or at least common question, but I couldn't find a satisfying answer on google or on SO.

I'm not sure when I should implement a destructor for my class.

An obvious case is when the class wraps a connection to a file, and I want to make sure the connection is closed so I close it in the destructor.

But I want to know in general, how can I know if I should define a destructor. What guidelines are there that I can check to see if I should have a destructor in this class?

One such guideline I can think of, is if the class contains any member pointers. The default destructor would destory the pointers on deletion, but not the objects they're pointing at. So that should be the work of a user-defined destructor. E.g: (I'm a C++ newbie, so this code might not compile).

class MyContainer {
public:
    MyContainer(int size) : data(new int[size]) { }
    ~MyContainer(){
        delete [] data;
    }
    // .. stuff omitted
private:
    int* data;
}

If I hadn't supplied that destructor, than destroying a MyContainer object would mean creating a leak, since all the data previously referenced by data wouldn't have been deleted.

But I have two questions:

1- Is this the only 'guideline'? I.e. define a destructor if the class has member pointers or if it's managing a resource? Or is there anything else?

2- Are there cases when I should not delete member pointers? What about references?

Aviv Cohn
  • 15,543
  • 25
  • 68
  • 131
  • You're looking at it backwards. If your program will have a memory or resource leak then you need to do something about it; one of those things could be to have a class that manages the resource by freeing the resource in its destructor. – M.M Oct 07 '14 at 23:35
  • In your code sample, you don't *need* a destructor; instead you could change the data member to be `vector` . or `shared_ptr`. – M.M Oct 07 '14 at 23:37
  • 4
    [Almost never](https://web.archive.org/web/20150427230710/http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html). – Jerry Coffin Oct 07 '14 at 23:48
  • 2
    @BenjaminLindley But seriously, I do find that at the beginning, looking for 'guidelines' and 'best practices' helps me progress. Idk, it feels helpful. – Aviv Cohn Oct 08 '14 at 00:01
  • You can make C++ more like Java (by which I mean safer and automatic memory management rather than copying Java style directly). The key is `new == BAD`. Instead of that dynamic array, use `std::vector` and you no longer need an explicit destructor, copy constructor, assignment operator, delete..... – Neil Kirk Oct 08 '14 at 00:09

3 Answers3

32

You need to define a destructor if the default destruction does not suffice. Of course, this just punts the question: what does the default destructor do? Well, it calls the destructors of each of the member variables, and that's it. If this is enough for you, you're good to go. If it's not, then you need to write a destructor.

The most common example is the case of allocating a pointer with new. A pointer (to any type) is a primitive, and the destructor just makes the pointer itself go away, without touching the pointed to memory. So the default destructor of a pointer does not have the right behavior for us (it will leak memory), hence we need a delete call in the destructor. Imagine now we change the raw pointer to a smart pointer. When the smart pointer is destroyed, it also calls the destructor of whatever its pointing to, and then frees the memory. So a smart pointer's destructor is sufficient.

By understanding the underlying reason behind the most common case, you can reason about less common cases. It's true that very often, if you're using smart pointers and std library containers, their destructors do the right thing and you don't need to write a destructor at all. But there are still exceptions.

Suppose you have a Logger class. This logger class is smart though, it buffers up a bunch of messages to Log, and then writes them out to a file only when the buffer reaches a certain size (it "flushes" the buffer). This can be more performant than just dumping everything to a file immediately. When the Logger is destroyed, you need to flush everything from the buffer regardless of whether it's full, so you'll probably want to write a destructor for it, even though its easy enough to implement Logger in terms of std::vector and std::string so that nothing leaks when its destroyed.

Edit: I didn't see question 2. The answer to question 2 is that you should not call delete if it is a non-owning pointer. In other words, if some other class or scope is solely responsible for cleaning up after this object, and you have the pointer "just to look", then do not call delete. The reason why is if you call delete and somebody else owns it, the pointer gets delete called on it twice:

struct A {
  A(SomeObj * obj) : m_obj(obj){};
  SomeObj * m_obj;
  ~A(){delete m_obj;};
}

SomeObj * obj = new SomeObj();
A a(obj);
delete obj; // bad!

In fact, arguably the guideline in c++11 is to NEVER call delete on a pointer. Why? Well, if you call delete on a pointer, it means you own it. And if you own it, there's no reason not to use a smart pointer, in particular unique_ptr is virtually the same speed and does this automatically, and is far more likely to be thread safe.

Further, furthermore (forgive me I'm getting really into this now), it's generally a bad idea to make non-owning views of objects (raw pointers or references) members of other objects. Why? Because, the object with the raw pointer may not have to worry about destroying the other object since it doesn't own it, but it has no way of knowing when it will be destroyed. The pointed to object could be destroyed while the object with the pointer is still alive:

struct A {
  SomeObj * m_obj;
  void func(){m_obj->doStuff();};
}

A a;
if(blah) {
  SomeObj b;
  a.m_obj = &b;
}
a.func() // bad!

Note that this only applies to member fields of objects. Passing a view of an object into a function (member or not) is safe, because the function is called in the enclosing scope of the object itself, so this is not an issue.

The harsh conclusion of all this is that unless you know what you're doing, you just shouldn't ever have raw pointers or references as member fields of objects.

Edit 2: I guess the overall conclusion (which is really nice!) is that in general, your classes should be written in such a way that they don't need destructors unless the destructors do something semantically meaningful. In my Logger example, the Logger has to be flushed, something important has to happen before destruction. You should not write (generally) classes that need to do trivial clean-up after their members, member variables should clean up after themselves.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • 1
    Great answer, thanks! I want to understand your example regarding an object holding a view (pointer or reference) to an obkect that it doesn't conceptually own: in you example, the pointer is only set to a value in a certain case, but we always operate on it from the outside, not knowing whether it's valid or not. So that's why an object shouldn't own a pointer to an object it doesnt own. Correct? – Aviv Cohn Oct 08 '14 at 06:15
  • Aviv, it only being set in one branch of an if is also a valid point. However, my example doesn't really depend on that. Even if you change blah to true (so it always enters the branch), you will still have a problem. The issue is one of scope, b is destroyed at the end of that if block. So as you can see there are multiple issues. It boils down to: data members of classes should be stuff they own, whether unique or shared ownership. If you don't own something, class methods are free to ask for this pointer each time they're called. This isn't an iron rule, but it is a very strong principle. – Nir Friedman Oct 08 '14 at 14:46
2

A class needs a destructor when it "owns" a resource and is responsible for cleaning it up. The purpose of the destructor is not simply to make the class itself work properly, but to make the program as a whole work properly: If a resource needs to be cleaned up, something needs to do it, and so some object should take responsibility for the cleanup.

For instance, memory might need to be freed. A file handle might need to be closed. A network socket might need to be shut down. A graphics device might need to be released. These things will stay around if not explicitly destroyed, and so something needs to destroy them.

The purpose of a destructor is to tie a resource's lifetime to an object's, so that the resource goes away when the object goes away.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
1

A Destructor is useful for when your classes contain Dynamically Allocated Memory. If your classes are simple and don't have 'DAM', then it's safe to not use a Destructor. In addition, read about the Rule Of Three. You should also add a copy constructor and an overloaded = operator if your class is going to have 'DAM'.

2) Do not worry about References. They work in a different way such as that it "Refers" to another variable (Which means they don't point to the memory).

  • And 'dynamically allocated memory' is always pointers, right? I mean you allocate something dynamically with `new`, and `new` returns a pointer. (Yeah I'm a newb) – Aviv Cohn Oct 07 '14 at 23:38
  • Yes, that is correct. I also added new information that you might want to read. –  Oct 07 '14 at 23:38
  • Btw, is it ever necessary (or possible) to call `delete regularNonPointerObjectVarible`? – Aviv Cohn Oct 07 '14 at 23:43
  • @AvivCohn Never necessary. And I'm unsure if it causes an error or a crash. I'll have to test. – David G Oct 07 '14 at 23:44
  • @0x499602D2 Oh actually I see why now, because a regular object is always stack-allocated, and stack-allocated objects are cleaned up automatically at the end of their scope, right? Also, that means that `delete reference` doesn't make sense either, because `reference` in an expression is replaced 'instantly' by the object it's referencing. Right? – Aviv Cohn Oct 07 '14 at 23:47
  • [Why you should not do it](http://stackoverflow.com/questions/4355468/is-it-possible-to-delete-a-non-new-object) @AvivCohn . It is possible, but DEFINITELY not recommended. –  Oct 07 '14 at 23:47
  • 2
    This isn't really correct. Your class might be managing some other resource, such as a file or a socket, which requires destruction. References may require deletion (although this is considered bad style) – M.M Oct 07 '14 at 23:48