Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

copy & swap in base and derived class

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);
}
like image 733
gartenriese Avatar asked Sep 22 '14 11:09

gartenriese


People also ask

What do you mean copy?

1 : an imitation, transcript, or reproduction of an original work (such as a letter, a painting, a table, or a dress) 2 : one of a series of especially mechanical reproductions of an original impression also : an individual example of such a reproduction.

What is another word for copy?

Some common synonyms of copy are duplicate, facsimile, replica, and reproduction. While all these words mean "a thing made to closely resemble another," copy applies especially to one of a number of things reproduced mechanically. printed 1000 copies of the lithograph.

What does copy mean in writing?

Broadly defined, copy is text within a publication or composition. It is in contrast to any graphic or pictorial aspects of a publication, article, or another kind of composition. Alternate definition: A reproduction of an original work.


2 Answers

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.

like image 172
Howard Hinnant Avatar answered Sep 20 '22 12:09

Howard Hinnant


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.
like image 41
Deduplicator Avatar answered Sep 22 '22 12:09

Deduplicator