20

The rule of 3 (the rule of 5 in the new c++ standard) states :

If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.

But, on the other hand, the Martin's "Clean Code" advises to remove all empty constructors and destructors (page 293, G12:Clutter) :

Of what use is a default constructor with no implementation? All it serves to do is clutter up the code with meaningless artifacts.

So, how to handle these two opposite opinions? Should empty constructors/destructors really be implemented?


Next example demonstrates exactly what I mean :

#include <iostream>
#include <memory>

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    ~A(){}
    A( const A & other ) : v( new int( *other.v ) ) {}
    A& operator=( const A & other )
    {
        v.reset( new int( *other.v ) );
        return *this;
    }

    std::auto_ptr< int > v;
};
int main()
{
    const A a( 55 );
    std::cout<< "a value = " << *a.v << std::endl;
    A b(a);
    std::cout<< "b value = " << *b.v << std::endl;
    const A c(11);
    std::cout<< "c value = " << *c.v << std::endl;
    b = c;
    std::cout<< "b new value = " << *b.v << std::endl;
}

Compiles fine using g++ 4.6.1 with :

g++ -std=c++0x -Wall -Wextra -pedantic example.cpp

The destructor for struct A is empty, and not really needed. So, should it be there, or should it be removed?

15
  • 16
    The 2 quotes talk about different things. Or I totally miss your point. Apr 12, 2012 at 11:18
  • 1
    @honk In the coding standard of my team, we have a rule to always declare all 4 (constructor, destructor, copy constructors). I was wondering if it really makes sense to do. Do I really have to always declare destructors, even if they are empty? Apr 12, 2012 at 12:35
  • As for empty desctructors think about this: codesynthesis.com/~boris/blog/2012/04/04/…. Otherwise the rule of 3(5) makes perfect sense to me, no idea why one would want a rule of 4. Apr 12, 2012 at 12:40
  • @honk Watch out about information you find on the net. Not all is true. For example, virtual ~base () = default; doesn't compile (with a good reason) Apr 12, 2012 at 13:04
  • @VJovic, No you don't have to declare an empty destructor, unless you need to make it virtual. And while we are on the subject, you should not be using auto_ptr either.
    – Dima
    Apr 12, 2012 at 20:23

6 Answers 6

44

For a start the rule says "probably", so it doesn't always apply.

The second point I see here is that if you have to declare one of the three, that's because it's doing something special like allocating memory. In this case, the others wouldn't be empty since they would have to handle the same task (such as copying the content of dynamically allocated memory in the copy constructor or freeing such memory).

So as a conclusion, you shouldn't declare empty constructors or destructors, but it's very likely that if one is needed, the others are needed too.

As for your example: In such a case, you can leave the destructor out. It does nothing, obviously. Usage of smart pointers is a perfect example of where and why the rule of 3 doesn't hold.

It's just a guide for where to take a second look over your code in case you may have forgotten to implement important functionality you might otherwise have missed.

4
  • With the use of smart pointers, the destructors are empty in most cases (I'd say >99% of destructors in my code base is empty, because almost every class uses the pimpl idiom). Apr 12, 2012 at 12:39
  • Wow, that's so much pimpling I'd call it smelly. With many compilers pimpled will be harder to optimize (e.g. harder to inline). Apr 12, 2012 at 12:46
  • @honk What do you mean by "many compilers pimpled"? :) Apr 12, 2012 at 12:50
  • @VJovic: sorry, typo: 'pimpled code' Apr 12, 2012 at 12:52
4

There is really no contradiction here. The rule of 3 talks about the destructor, the copy constructor and the copy assignment operator. Uncle Bob talks about empty default constructors.

If you need a destructor, then your class probably contains pointers to dynamically allocated memory, and you probably want to have a copy ctor and an operator=() that do a deep copy. This is completely orthogonal to whether or not you need a default constructor.

Note also that in C++ there are situations when you do need a default constructor, even if it is empty. Let's say your class has a non-default constructor. In that case, the compiler will not generate a default constructor for you. That means that objects of this class cannot be stored in STL containers, because those containers expect the objects to be default-constructable.

On the other hand, if you are not planning to ever put the objects of your class into STL containers, an empty default constructor certainly is useless clutter.

2

Here your potential (*) equivalent to the default one constructor/assignment/destructor has a purpose: document the fact you have though about the issue and determined that the default behavior was correct. BTW, in C++11, things have not stabilized enough to know if =default can serve that purpose.

(There is another potential purpose: provide an out of line definition instead of the default inline one, better to document explicitly if you have any reason to do so).

(*) Potential because I don't remember a real life case where the rule of three didn't apply, if I had to do something in one, I had to do something in the others.


Edit after your addition of an example. your example using auto_ptr is interesting. You are using a smart pointer, but not one which is up to the job. I'd rather write one which is -- especially if the situation occurs often -- than doing what you did. (If I'm not mistaken, neither the standard nor boost provide one).

1
  • The example demonstrates my point. The destructor is not really needed, but the rule of 3 tells it should be there. Apr 12, 2012 at 19:48
1

The rule of 5 is a cautalative extension of the rule of 3 that is a cautelative behavior againt possible object misuse.

If you need to have a destructor, it means that you did some "resource management" other than the default (just construct and destruct values).

Since copy, assign, move and transfer by default copy values, if you are not holding just values, you have to define what to do.

That said, C++ deletes teh copy if you define the move and deletes the move if you define the copy. In the most of the case you have to define if you want to emulate a value (hence copy mut clone the resource, and move has no sense) or a resource manager (and hence move the resource, where copy has no sense: the rule of 3 becomes the rule of the other 3)

The cases when you have to define both copy and move (rule of 5) are quite rare: typically you have "big value" that has to be copyed if given to distinct objects, but can be moved if taken from a temporary object (avoiding a clone then destroy). That's the case for STL containers or arithmetic containers.

A case can be matrixes: they have to support copy because they are values, (a=b; c=b; a*=2; b*=3; must not influence each other) but they can be optimized by supporting also moving ( a = 3*b+4*c has a + that takes two temporaries and generates a temporary: avoiding clone and delete can be useful)

1

I prefer a different phrasing of the rule of three, which seems more reasonable, which is "if your class needs a destructor (other than an empty virtual destructor) it probably also needs a copy constructor and assignment operator."

Specifying it as a one way relationship from the destructor makes a few things clearer:

  1. It doesn't apply in cases where you provide a non-default copy constructor or assignment operator as an optimization only.

  2. The reason for the rule is that the default copy constructor or assignment operator can screw up manual resource management. If you're manually managing resources, you're likely to have realized that you'll need a destructor to release them.

-3

There is another point not mentioned in the discussion yet: A destructor should always be virtual.

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

The constructor needs to be declared as virtual in the base class to make it virtual in all derived classes too. So, even if your base class does not need a destructor, you end up declaring and implementing an empty destructor.

If you put on all warnings on (-Wall -Wextra -Weffc++) g++ will warn you about this. I consider it good practice to always declare a virtual destructor in any class, because you never know, if your class will eventually become a base class. If the virtual destructor is not needed, it does no harm. If it is, you save time to find the error.

2
  • 1
    But I don't want the virtual constructor. If I do that, then every call to any method would use virtual dispatch. btw take a note that there are no such thing as "virtual constructor" in c++. Also, I compiled the example as very high warning level. Apr 14, 2012 at 7:07
  • IIRC, the rule that gcc uses for its warning, and the rule I generally follow anyway, is that there should be a virtual destructor if there are any other virtual methods in the class.
    – Jules
    Dec 25, 2014 at 20:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.