23

Let's assume we have a very basic class A:

class A {
    public:
        void SetName(const std::string& newName) {
            m_name=newName;
        }

        void Print() const {
            std::printf("A::Print(). Name: %s\n",m_name.c_str());
        }
    private:
        std::string m_name;  
};

We want to extend this class with class B so we add our virtual destructor, change a member to virtual and change private to protected for inh:

class A {
    public:
        virtual ~A() {}

        void SetName(const std::string& newName) {
            m_name=newName;
        }

        virtual void Print() const {
            std::printf("A::Print(). Name: %s\n",m_name.c_str());
        }
    protected:
        std::string m_name;

};

class B : public A {
    public:
        virtual void Print() const {
            std::printf("B::Print(). Name: %s\n",m_name.c_str());
        }
};

Now since we added a destructor in class A do we need to create a copy constructor and copy operator like so?

class A {
    public:
        virtual ~A() {}

        A() = default;
        A(const A& copyFrom){
            *this = copyFrom;
        }
        virtual A& operator=(const A& copyFrom){
            m_name=copyFrom.m_name;
            return *this;
        };

        void SetName(const std::string& newName) {
            m_name=newName;
        }

        virtual void Print() const {
            std::printf("A::Print(). Name: %s\n",m_name.c_str());
        }
    protected:
        std::string m_name;

};

To me this seems unnecessary as the default copy operator and copy constructor would do the same thing.

Grapes
  • 2,345
  • 3
  • 23
  • 41
  • 2
    @MarcoA. A derived class's destructor may need to do something extra, and you need the base class destructor to be virtual if you want to delete a derived from a pointer to base. – T.C. Sep 25 '14 at 13:36
  • @Angew that is a good reason. Right – Marco A. Sep 25 '14 at 13:40
  • Note: avoid using `protected` data members in general; because unless you tightly control the inheritance hierarchy (`final`), you have lost all guarantees about the state of this member in general. This is similar to returning a non-const reference. – Matthieu M. Sep 25 '14 at 14:02

3 Answers3

26

To be prepared for potential future evolution of the language, you should indeed explicitly default the copy/move constructors and assignment operators when you add a virtual destructor. That's because C++11, 12.8/7 makes implicit generation of copy constructors deprecated when the class has a user-declared destructor.

Fortunately, C++11's explicit defaulting makes their definition easy:

class A {
    public:
        virtual ~A() {}

        A() = default;
        A(const A& copyFrom) = default;
        A& operator=(const A& copyFrom) = default;
        A(A &&) = default;
        A& operator=(A &&) = default;

        void SetName(const std::string& newName) {
            m_name=newName;
        }

        virtual void Print() const {
            std::printf("A::Print(). Name: %s\n",m_name.c_str());
        }
    protected:
        std::string m_name;

};
Angew is no longer proud of SO
  • 161,995
  • 14
  • 331
  • 433
16

The rule of three applies to everything.

If your class is intended to be used as a polymorphic base, it's highly unlikely you will want to use its copy constructor because it slices. So you have to make a decision. That's what the rule of three is about: you can't choose to have a destructor without considering the copy special members.

Note that the rule of three doesn't say you're supposed to implement the copy constructor and copy assignment operator. You're supposed to deal with them somehow, because the default-generated one is highly likely not suitable if you have your own destructor (it slices!), but the way you deal with them doesn't have to be implementing them.

You should probably just forbid it since using polymorphic bases and value semantics tend to mix like water and oil.

I guess you could maybe make it protected so derived classes can call it for their own copies, though I still consider that a questionable choice.

Additionally, since C++11 the generation of copy special members is deprecated when a destructor is user-declared. That means that if you want your code to be forward-compatible, even if you want the default copy constructor behaviour (a questionable choice), you will want to make that explicit. You can use = default for that.

R. Martinho Fernandes
  • 219,040
  • 71
  • 423
  • 503
  • 1
    This is better than the accepted answer. No response here is complete without mentioning slicing. Also, "polymorphic bases and value semantics tend to mix like water and oil" is a very good observation and worth considering if (like me) you are searching for this Q&A. I suppose wacky type-erasure stuff could create an exception, but that is far from the common case. – Nemo Jan 18 '18 at 20:14
5

If the destructor doesn't do anything, then there's (usually) no need for the copy/move operations to do anything other than the default. There's certainly no need to write versions that do what the defaults would, just to satisfy an over-simplification of the rule. All that does is increase the complexity of the code and the scope for error.

However, if you do declare a virtual destructor, indicating that the class is intended to be a polymorphic base class, you might consider deleting the copy/move operations to prevent slicing.

This article gives a useful wording for the rule, including

If a class has a nonempty destructor, it almost always needs a copy constructor and an assignment operator.

Mike Seymour
  • 242,813
  • 27
  • 432
  • 630