Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reusing the copy-and-swap idiom

I'm trying to put the copy-and-swap idiom into a reusable mixin:

template<typename Derived>
struct copy_and_swap
{
    Derived& operator=(Derived copy)
    {
        Derived* derived = static_cast<Derived*>(this);
        derived->swap(copy);
        return *derived;
    }
};

I intend it to be mixed in via CRTP:

struct Foo : copy_and_swap<Foo>
{
    Foo()
    {
        std::cout << "default\n";
    }

    Foo(const Foo& other)
    {
        std::cout << "copy\n";
    }

    void swap(Foo& other)
    {
        std::cout << "swap\n";
    }
};

However, a simple test shows that it is not working:

Foo x;
Foo y;
x = y;

This only prints "default" twice, neither "copy" nor "swap" is printed. What am I missing here?

like image 803
fredoverflow Avatar asked Aug 16 '11 14:08

fredoverflow


2 Answers

This:

 Derived& operator=(Derived copy)

doesn't declare a copy assignment operator for the base class (it has the wrong signature). So the default generated assignment operator in Foo will not use this operator.

Remember 12.8:

A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.) [Note: an overloaded assignment operator must be declared to have only one parameter; see 13.5.3. ] [Note: more than one form of copy assignment operator may be declared for a class. ] [Note: if a class X only has a copy assignment operator with a parameter of type X&, an expression of type const X cannot be assigned to an object of type X.

EDIT don't do this (can you see why ?):

You can do:

template<typename Derived>
struct copy_and_swap
{
    void operator=(const copy_and_swap& copy)
    {
        Derived copy(static_cast<const Derived&>(copy));
        copy.swap(static_cast<Derived&>(*this));
    }
};

but you lose the potential copy elision optimization.

Indeed, this would assign twice the members of derived classes: once via copy_and_swap<Derived> assignment operator, and once via the derived class' generated assignment operator. To correct the situation, you'd have to do (and not forget to do):

struct Foo : copy_and_swap<Foo>
{

    Foo& operator=(const Foo& x)
    {
        static_cast<copy_and_swap<Foo>&>(*this) = x;
        return *this;
    }

private:
    // Some stateful members here
}

The moral of the story: don't write a CRTP class for the copy and swap idiom.

like image 116
Alexandre C. Avatar answered Nov 07 '22 03:11

Alexandre C.


You cannot inherit assignment operators as a special case, if memory correctly serves. I believe that they can be explicitly using'd in if you need.

Also, be careful about over use of copy-and-swap. It produces non-ideal results where the original has resources that could be re-used to make the copy, such as containers. Safety is guaranteed but optimum performance is not.

like image 41
Puppy Avatar answered Nov 07 '22 01:11

Puppy