I recently read about copy & swap and am now trying to implement the ctors in a base and derived class. I have the four constructors in both my base and derived class, however I am unsure how to implement the assignment operator of the derived class.
explicit Base(int i) : m_i{i} {}
Base(const Base & other) : m_i{other.m_i}
Base(Base && other) : Base(0) { swap(*this, other); }
Base & operator=(Base other) { swap(*this, other); return *this; }
friend void swap(Base & a, Base & b) noexcept {
using std::swap;
swap(a.m_i, b.m_i);
}
explicit Derived(int j) : Base(42), m_j(j) {}
Derived(const Derived & other) : Derived(other.m_j) {}
Derived(Derived && other) : Derived(other.m_j) { swap(*this, other); }
Derived & operator=(Derived other) { /*???*/ }
friend void swap(Derived & a, Derived & b) noexcept {
using std::swap;
swap(a.m_j, b.m_j);
}
Consider using = default as much as possible. And if we are talking about public inheritance, you really need a virtual destructor as well.
Here is how your Base would look using the copy/swap style:
class Base
{
int m_i;
public:
virtual ~Base() = default;
Base(const Base& other) = default;
Base& operator=(Base other) noexcept
{
swap(*this, other);
return *this;
}
Base(Base&& other) noexcept
: Base(0)
{
swap(*this, other);
}
explicit Base(int i) noexcept
: m_i{i}
{}
friend void swap(Base& a, Base& b) noexcept
{
using std::swap;
swap(a.m_i, b.m_i);
}
};
The only difference from what you have is that I've added the virtual destructor, and used = default for the copy constructor.
Now for Derived:
class Derived
: public Base
{
int m_j;
public:
Derived(const Derived& other) = default;
Derived& operator=(Derived other) noexcept
{
swap(*this, other);
return *this;
}
Derived(Derived&& other) noexcept
: Derived(0)
{
swap(*this, other);
}
explicit Derived(int j) noexcept
: Base(42)
, m_j{j}
{}
friend void swap(Derived& a, Derived& b) noexcept
{
using std::swap;
swap(static_cast<Base&>(a), static_cast<Base&>(b));
swap(a.m_j, b.m_j);
}
};
I've let the compiler implicitly take care of the destructor since the compiler will implicitly give me a virtual one that does the right thing in this case.
Again I've explicitly defaulted the copy constructor. This corrects a bug in your version which neglects to copy Base.
The operator= looks just like the Base version.
The Derived move constructor does not need to move or copy anything from other since it is going to swap with other.
The Derived swap function must swap the Base part as well as the Derived part.
Now consider not using the copy/swap idiom. This can be surprisingly easier, and in some cases, higher performing.
For Base you can use = default for all 5 of your special members:
class Base
{
int m_i;
public:
virtual ~Base() = default;
Base(const Base&) = default;
Base& operator=(const Base&) = default;
Base(Base&&) = default;
Base& operator=(Base&&) = default;
explicit Base(int i) noexcept
: m_i{i}
{}
friend void swap(Base& a, Base& b) noexcept
{
using std::swap;
swap(a.m_i, b.m_i);
}
};
The only work that is really required here is your custom constructor and swap function.
Derived is even easier:
class Derived
: public Base
{
int m_j;
public:
explicit Derived(int j) noexcept
: Base(42)
, m_j{j}
{}
friend void swap(Derived& a, Derived& b) noexcept
{
using std::swap;
swap(static_cast<Base&>(a), static_cast<Base&>(b));
swap(a.m_j, b.m_j);
}
};
All 5 of the special members can be implicitly defaulted!
We couldn't default them in the Base because we needed to specify the virtual destructor, which inhibits the generation of the move members, and the generation of the copy members is deprecated with a user-declared destructor. But since we do not need to declare the destructor in Derived, we can just let the compiler handle everything.
As one of the big selling points of copy/swap is reduced coding, it can be ironic that using it can actually require more coding than letting the compiler default the special members.
Of course if the defaults do not do the right thing, then don't use them. I'm simply saying that the defaults should be your first choice, ahead of copy/swap.
You implement op= exactly the same way for Derived as for Base:
Derived& operator=(Derived other) { swap(*this, other); return *this; }
I hope you are aware of the up- and down-sides of passing the argument by value there, though:
Up-side: Only one function needed for all value categories.
Down-Side: Second move for xvalues, move in addition to the needed copy for prvalues.
Other points to consider:
Rule-of-thumb: Single-argument non-copy/move ctors should be explicit: You really don't want to have an implicit conversion from int to Base...
You forgot to re-implement swap for Derived (swap all sub-objects, both base and member). You might forego it if Derived does not add any members though.
Related
Suppose I'm dealing with the following header file(for experimental purposes):
class Base{
protected:
Data& data1;
public:
Base(Data& data1);
virtual Base* clone() const =0;
virtual ~Base();
virtual void do() = 0;
};
class Derived: public Base{
private:
const int data2;
public:
Derived(int data2, Data& data1);
virtual Derived* clone() const;
~Derived();
}
I tried to implement these classes which are fairly simple:
Base:
Base::Base(Data& data1): data1{data1} {}
virtual Base::~Base() = default;
Derived:
Derived::Derived(int data2, Data& data1): Base(data1), data2{data2} {}
virtual Derived* Derived::clone() const {
return new Derived(*this);
}
virtual void Derived::do() {
return;
}
So far so good, but I'm having a couple of issues:
Say I wanted to implement a copy constructor and a copy assignment operator for this hierarchy,
should I do it in both classes? If so, is this a good way to do it? :
Base:
Base::Base(const Base& other): Base(other.data1) {}
Base& Base::operator=(const Base& rhs){
if(this == &rhs)
return *this;
data1 = rhs.data1; // is this possible? since data1 is a reference (cannot be reassigned)
return *this;
}
and which is a proper way to do so for derived?:
Derived:
Derived::Derived(const Derived& other): Base(other.data1), data2{other.data2} {}
OR
Derived::Derived(const Derived& other): Derived(other.data2, other.data2) {}
Derived& Derived::operator=(const Derived& rhs){
if(this == &rhs)
return *this;
data1 = rhs.data1;
data2 = rhs.data2; // data2 is a const variable so this is probably invalid too
return *this;
}
Once I implement a copy constructor for derived, I get an 'Endless loop' warning when returning from clone() method. Why is that?
Is there a proper way to implement rule of 3/ rule of 5 for classes of such hierarchy?
Thanks ahead.
Say I wanted to implement a copy constructor and a copy assignment operator for this hierarchy, should I do it in both classes? If so, is this a good way to do it?
Yes, you have to provide copy constructor/assignment.
Best way, when possible/correct, is to use default.
For assignment, your issue is unrelated to hierarchy, but related to member you use:
Reference cannot be rebound. const member cannot be changed.
So default assignment cannot be generated, mark them as deleted.
class Base
{
// ...
Base(const Base&) = default;
Base& operator=(const Base&) = delete;
};
class Derived
{
// ...
Derived(const Derived&) = default;
Derived& operator=(const Derived&) = delete;
};
Once I implement a copy constructor for derived, I get an 'Endless loop' warning when returning from clone() method. Why is that?
Cannot reproduce from comment
Is there a proper way to implement rule of 3/ rule of 5 for classes of such hierarchy?
Hierarchy is unrelated to rule of 5/3/0.
Just respect rule of 5/3/0 for each class.
There is well-known clone idiom for copying Derived objects via pointer to Base class.
class Base{
int b;
public:
virtual unique_ptr<Base> clone() const = 0;
virtual ~Base() = default;
};
class Derived : public Base {
int d;
public:
virtual unique_ptr<Base> clone() const override {
return std::make_unique<Derived>(*this);
}
}
However, I can't find clear instructions how to define copy constructors and assignments in this case. This is how I suppose it should be done in Base class:
class Base {
protected:
Base(const Base&) = default;
private:
Base& operator=(const Base&) = delete;
}
Is it necessary (in order to avoid potential slices)? Is it right way to do it? Does it suffice or should I add such declarations to Derived class as well?
As derived classes use the copy constructor to create clones, you may like to make the copy constructors non-public to avoid accidental slicing but accessible to derived classes.
protected fills this requirement. It needs to be applied to each class' copy constructor because the compiler-generated copy constructor is public. It also makes sense to apply the same treatment to the assignment operator.
That also prevents std::make_unique from accessing the copy constructor though:
class A
{
protected:
A(A const&) = default;
A& operator=(A const&) = default;
public:
A();
virtual std::unique_ptr<A> clone() const = 0;
};
class B : public A
{
protected:
B(B const&) = default;
B& operator=(B const&) = default;
public:
B();
std::unique_ptr<A> clone() const override {
return std::unique_ptr<A>(new B(*this));
}
};
Deleting the copy assignment operator is probably a good idea, unless you need it.
Deleting operator=(const Base&) in Base is enough, as the implicitly declared copy assignment operator is defined as deleted for a derived class if the base class has no copy assignment operator (see cppreference.com).
If you really want copy assignment you can make the copy assignment operator virtual, and carefully implement the correct behaviour in the derived classes by
calling Base::operator= to assign the Base class members, and
assigning the members of the derived class, using dynamic_cast to ensure that the argument is of the correct type .
If done correctly, this avoids object slicing and retains the correct type.
An example (with copy constructor details omitted):
struct Point {
virtual Point& operator=(const Point& p) =default;
int x;
int y;
};
struct Point3d :public Point{
virtual Point3d& operator=(const Point& p);
int z;
};
Point3d& Point3d::operator=(const Point& p)
{
Point::operator=(p);
auto p3d = dynamic_cast<const Point3d*>(&p);
if(p3d){
z = p3d->z;
} else {
z = 0;
}
return *this;
}
Assume we have this two classes, with implemented swap-idioms. The copy constructor and assignment operator of the base class are deleted, as it makes no sense. However the swap-method is implemented, as it holds a member.
namespace std
{
template<>
void swap<Base>(Base& a, Base& b)
{
a.swap(b);
}
template<>
void swap<Derived>(Derived& a, Derived& b)
{
a.swap(b);
}
}
class Base
{
public:
Base(int ID) : ID_(ID) {}
virtual std::string getString()=0;
Base(const Base&)=delete;
operator=(const Base&)=delete;
void swap(Base& rhs)
{
std:swap(ID_, rhs.ID_);
}
private:
int ID_;
}
class Derived : public Base
{
public:
Derived(int ID, bool Value) : Base(ID), Value_(Value) {}
virtual ~Derived() {}
Derived(Derived& rhs)
{
std::swap(*this, rhs);
}
virtual std::string getString() {return Value_ ? "True" : "False"}
void swap(Derived& lhs, Derived& rhs)
{
std::swap(static_cast<Base&>(lhs), static_cast<Base&>(rhs);
std::swap(lhs.Value_, rhs.Value_);
}
private:
bool Value_;
}
As seen in many examples, this would be the standard way to do it I suppose.
However, I see a problem with the public Base::swap, as it should not be possible to swap only the abstract base-class!
Wouldn't it be better to remove the template for the Base class and make the Base::swap Method protected:
class Base
{
...
protected:
void swap(Base& rhs, Base &lhs);
}
class Derived : public Base
{
...
public:
void swap(Derived& lhs, Derived& rhs)
{
Base::swap(static_cast<Base&>(lhs), static_cast<Base&>(rhs);
std::swap(lhs.Value_, rhs.Value_);
}
}
Assuming, there is another class derived from base, with the first implementation it would be possible to swap the ID, however the data members of the derived objects would stay the same.
So, am I right thinking that swapping of a abstract class should not be possible from outside?
The copy constructor and assignment operator of the base class are deleted, as it makes no sense.
Actually, this is terrible. Now you made Derived uncopyable! Why? There is no reason to add this restriction. The default copy constructor and assignment operator of Base are perfectly reasonable in the context of copying the most-base class of the hierarchy.
Once you undo that, there's no need to do anything else as std::swap(derived1, derived2) would already do the right thing. The default move construction/operation is correct. It's always good to let the compiler do things for you.
But if you want to override swap anyway, the correct way to do that would be as a non-member friend:
class Base {
...
friend void swap(Base& lhs, Base& rhs) {
using std::swap;
swap(lhs.ID_, rhs.ID_);
}
};
class Derived : public Base {
...
friend void swap(Derived& lhs, Derived& rhs) {
using std::swap;
swap(static_cast<Base&>(lhs), static_cast<Base&>(rhs));
swap(lhs.Value_, rhs.Value_);
}
};
Also, your Derived copy constructor makes no sense. Remove it as per the first paragraph of my answer.
I've got a class that inherits from a MSFT class, and therefore cannot be changed, and I'd like my derived class to have identical behavior for its copy constructor and copy assignment operator. The trouble I'm having is that in the copy constructor, you are free to invoke a constructor for the base class in the initializer list, but in the operator, this is not an option. How can I properly recreate this behavior in the assignment operator? Is it sufficient to just call the base class's constructor in the body of the operator overload?
Additional note: the base class inherits from CObject, which has operator=() and the copy constructor as private and unimplemented methods, so unfortunately any calls to those will result in a compile error.
I've provided a simplified code scenario below:
Class declarations:
class Base
{
protected:
int baseInt;
public:
Base(int);
}
class Derived : public Base
{
public:
Derived(const Derived& other);
Derived& operator=(const Derived& rhs);
private:
int derivedInt;
}
Derived class member functions:
// Copy Constructor
Derived(const Derived& other) : Base(5)
{
derivedInt = other.derivedInt;
}
// Copy Assignment Operator
Derived& operator=(const Derived& rhs)
{
if (&rhs != this)
{
derivedInt = other.derivedInt;
return *this;
}
}
EDIT: Updated syntax and added CObject note
In the general case, you do that either by explicitly calling operator= for the base class subobject, or by using the copy&swap idiom, if available:
//explicit call to base operator=
Derived& operator=(const Derived& rhs)
{
Base::operator=(rhs); //
derivedInt = rhs.derivedInt;
return *this;
}
//or copy&swap:
Derived& operator=(const Derived& rhs)
{
Derived tmp(rhs); //copy
tmp.swap(*this);
return *this;
}
void swap(Derived& other)
{
Base::swap(other);
std::swap(derivedInt,other.derivedInt);
}
Update: since your base class is not meant to be copy-assigned, your derived class should not be copy-assigned either. There are some cases where classes contain noncopyable parts but are perfectly copyable. In those cases the noncopyable part is something that does not directly contribute to the class objects' state, e.g. a unique ID. Then those parts will normally not be changed during assignment. However, in those cases the noncopyable parts should not be contained by inheritance but rather by aggregation.
Shortly said: Iheritance means "is-A" relationship. Your Base cannot be copied. Your Derived is a Base. Thus your Derived cannot be copied either.
Like this
Derived& operator=(const Derived& rhs)
{
Base::operator=(rhs);
derivedInt = rhs.derivedInt;
return *this;
}
Is following pattern ok/safe ? Or are there any shortcomings ?
(I also use it for equality operators)
Derived& operator=(const Derived& rhs)
{
static_cast<Base&>(*this) = rhs;
// ... copy member variables of Derived
return *this;
}
This is fine, but it's a lot more readable IMHO to call the base-class by name:
Base::operator = (rhs);
Yes, it's safe.
A different syntax to do the same thing could be:
Base::operator=( rhs );
That's better to use
Base::operator=(rhs);
because if your base class have a pure virtual method the static_cast is not allowed.
class Base {
// Attribute
public:
virtual void f() = 0;
protected:
Base& operator(const Base&);
}
class Derived {
public:
virtual void f() {};
Derived& operator=(const Derived& src) {
Base::operator=(src); // work
static_cast<Base&>(*this) = src; // didn't work
}
}