Reputation: 2583
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.
Upvotes: 27
Views: 7282
Reputation: 171167
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;
};
Upvotes: 28
Reputation: 234644
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.
Upvotes: 18
Reputation: 254691
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.
Upvotes: 5