5

I want to use the default functionality of the copy assignment operator but be able to do some additional tasks as part of the operation. So the basics form would look like this:

class Test
{
    void operator=(Test& that)
    {
        *this = that; //do the default copy operation
        this->foo()  //perform some other tasks
    }
};

This could easily be done by creating a copy() function but it would be nice to preserve the cleanliness of the "=" operation.

2 Answers2

3

You could use a base implementation class and delegate to the base operator= from the subclass

// Fill this class with your implementation.
class ClsImpl {
  // some complicated class
 protected:
  ~ClsImpl() = default;
};

class Cls : public ClsImpl {
 public:
  Cls& operator=(const Cls& other) {
    if (this == &other) { return *this; }
    // assign using the base class's operator=
    ClsImpl::operator=(other); // default operator= in ClsImpl
    this->foo(); // perform some other task
    return *this;
  }
};
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • 2
    Why are you using `static_cast` to force invoking the base class implementation instead of just invoking it explicitly via `ClsImpl::operator=(other);`. If `ClsImpl` is just a way to delegate the copy-assignment functionality you should probably use private inheritance to prevent implicit conversions and introducing the risk of slicing. – Captain Obvlious Aug 02 '16 at 23:46
  • 1
    @CaptainObvlious they amount to the same thing but omitting the cast is a gain, I agree. Private inheritance would be great if it didn't also likely require a ton of `using` declarations to expose all of the base class's member functions. – Ryan Haining Aug 02 '16 at 23:55
  • Yeah a lot of `using` statements would suck. Making the copy-constructor and copy-assignment operator protected in the base class would take care of the slicing anyway. – Captain Obvlious Aug 03 '16 at 00:01
0

One way is to derive again from the derived class to provide the post-copy logic.

#include <iostream>


// a wrapper class to provide custom copy actions

template<class Base>
struct CopyActions : Base
{
    using base_class = Base;
    using CopyActions::base_class::base_class;

    // copy operator will call the base and then perform custom action

    CopyActions& operator=(const CopyActions& r) {
        base_class::operator=(r);
        onCustomCopy(r, *this);
        return *this;
    }
};

// a class to notify us when a copy takes place, without having to write
// custom copy operators
struct copy_sentinel
{
    copy_sentinel operator=(const copy_sentinel&) {
        std::cout << "copying " << name << '\n';
        return *this;
    }
    const char* name;
};


int test_count = 0;

// a model base class
struct MyBase
{
    int base_count = test_count++;
    copy_sentinel base_s { "MyBase" };
};

// a model derived class containing only logic
struct MyDerived : MyBase
{
    int derived_count = test_count++;
    copy_sentinel base_s { "MyDerived" };
};

// a custom copy action (free function)
void onCustomCopy(const MyDerived& from, MyDerived& to)
{
    std::cout << "custom copy action\n";
}

// our derived class with custom copy actions
using SuperDerived = CopyActions<MyDerived>;

// test
int main()
{
    SuperDerived a;  // 0, 1
    SuperDerived b;  // 2, 3

    // prove initial values
    std::cout << a.base_count << ", " << a.derived_count << std::endl;

    // perform copy and report actions
    a = b;

    // prove a copy occurred
    std::cout << a.base_count << ", " << a.derived_count << std::endl;
}

expected results:

0, 1
copying MyBase
copying MyDerived
custom copy action
2, 3
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142