c++oopinheritanceupcasting

Overloading assignment operator and upcasting


I was wondering how should I (or can I? does it make any sense?) overload the assignment operator when working with inheritance and upcasting? Let's say we have Base class and Derived class (inherited from Base). If i have something like:

/// supose we have one overloaded assignment operator in Base like Base& operator=(const Base&) and 
///one in Derived like Derived& operator=(const Derived&)...
Base* a, *b;
Derived c,d;

a = &c;
b = &d;

*a = *b  /// this will call the function in Base

If that calls the Base function, why should I overload "=" again in Derived? Is overloading assignment operator in Derived necessary only for working directly with objects, not upcasting (pointers) ?


Solution

  • Here's some code that hopefully helps you out.

    Derived Does Not Own A Dynamic Resource
    The Base class holds a dynamic resource, so we are required to follow the Rule of 3 (should be 5, but kept it at 3 for brevity). I did so by utilizing the copy/swap idiom.

    I then derive Derived from Base. It does not hold a dynamic resource, so I follow the Rule of 0 and don't provide a custom copy constructor, destructor, assignment operator, move constructor, or move assignment.

    You can see from the output that the Base portion of the Derived objects are able to deep-copy themsleves just fine, and the Derived-only portion gets by just fine with shallow copy. The final output leaks memory, but I chose to do that to demonstrate an actual overwrite using a pointer to Base.

    #include <iostream>
    
    class Base {
     private:
      int* m = nullptr;
    
     public:
      Base() = default;
      Base(int v) : m(new int(v)) {}
      Base(const Base& other) : m(new int(*(other.m))) {}
      virtual ~Base() {
        delete m;
        m = nullptr;
      }
    
      Base& operator=(Base other) {
        swap(*this, other);
    
        return *this;
      }
    
      friend void swap(Base& lhs, Base& rhs) {
        using std::swap;
    
        swap(lhs.m, rhs.m);
      }
    
      virtual void print() const {
        std::cout << "Address: " << m << "\nValue: " << *m << '\n';
      }
    };
    
    class Derived : public Base {
     private:
      double x = 0.0;
    
     public:
      Derived() = default;
      Derived(double v) : Base(), x(v) {}
      Derived(int i, double v) : Base(i), x(v) {}
    
      void print() const override {
        std::cout << "Address: " << &x << "\nValue: " << x << '\n';
        Base::print();
      }
    };
    
    int main() {
      std::cout << "A\n";
      Base* a = new Derived(5, 3.14);
      a->print();
    
      std::cout << "\nB\n";
      Derived b = *(dynamic_cast<Derived*>(a));  // Copy ctor
      b.print();
    
      std::cout << "\nC\n";
      Derived c;
      c = b;
      c.print();
    
      std::cout << "\nReplace A (This leaks)\n";
      a = new Derived(7, 9.81);
      a->print();
    }
    

    Output:

    A
    Address: 0x21712d0
    Value: 3.14
    Address: 0x21712e0
    Value: 5
    
    B
    Address: 0x7ffdd62964c8
    Value: 3.14
    Address: 0x2171300
    Value: 5
    
    C
    Address: 0x7ffdd62964b0
    Value: 3.14
    Address: 0x2171320
    Value: 5
    
    Replace A (This leaks)
    Address: 0x2171350
    Value: 9.81
    Address: 0x2171360
    Value: 7
    

    Derived Owns A Dynamic Resource
    Now, Derived has a dynamic of its own to manage. So I follow the Rule of 3 and provide a copy constructor, destructor, and assignment operator overload. You'll notice that the assignment operator looks identical to the Base version; this is intentional.

    It's because I'm using the copy/swap idiom. So in the swap() function for Derived, I add a step where it swaps the Base portion, then swaps the Derived portion. I do this by invoking the Base swap() function through the dynamic cast.

    And we can again observe that all objects have their own memory for each dynamically allocated piece.

    #include <iostream>
    
    class Base {
     private:
      int* m = nullptr;
    
     public:
      Base() = default;
      Base(int v) : m(new int(v)) {}
      Base(const Base& other) : m(new int(*(other.m))) {}
      virtual ~Base() {
        delete m;
        m = nullptr;
      }
    
      Base& operator=(Base other) {
        swap(*this, other);
    
        return *this;
      }
    
      friend void swap(Base& lhs, Base& rhs) {
        using std::swap;
    
        swap(lhs.m, rhs.m);
      }
    
      virtual void print() const {
        std::cout << "Address: " << m << "\nValue: " << *m << '\n';
      }
    };
    
    class Derived : public Base {
     private:
      double* x = nullptr;
    
     public:
      Derived() = default;
      Derived(double v) : Base(), x(new double(v)) {}
      Derived(int i, double v) : Base(i), x(new double(v)) {}
      Derived(const Derived& other) : Base(other), x(new double(*(other.x))) {}
      ~Derived() {
        delete x;
        x = nullptr;
      }
    
      Derived& operator=(Derived other) {
        swap(*this, other);
    
        return *this;
      }
    
      friend void swap(Derived& lhs, Derived& rhs) {
        using std::swap;
    
        swap(dynamic_cast<Base&>(lhs), dynamic_cast<Base&>(rhs));
        swap(lhs.x, rhs.x);
      }
    
      void print() const override {
        std::cout << "Address: " << &x << "\nValue: " << *x << '\n';
        Base::print();
      }
    };
    
    int main() {
      std::cout << "A\n";
      Base* a = new Derived(5, 3.14);
      a->print();
    
      std::cout << "\nB\n";
      Derived b = *(dynamic_cast<Derived*>(a));  // Copy ctor
      b.print();
    
      std::cout << "\nC\n";
      Derived c;
      c = b;
      c.print();
    
      std::cout << "\nReplace A (This leaks)\n";
      a = new Derived(7, 9.81);
      a->print();
    }
    

    Output:

    A
    Address: 0x14812d0
    Value: 3.14
    Address: 0x14812e0
    Value: 5
    
    B
    Address: 0x7fffe89e8d68
    Value: 3.14
    Address: 0x1481320
    Value: 5
    
    C
    Address: 0x7fffe89e8d50
    Value: 3.14
    Address: 0x1481360
    Value: 5
    
    Replace A (This leaks)
    Address: 0x14813b0
    Value: 9.81
    Address: 0x14813c0
    Value: 7