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) ?
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