I was experimenting with making the operator= be virtual, like so:
#include <iostream>
#include <string>
class Base {
public:
std::string field1;
Base(std::string str) : field1(str) {}
virtual Base& operator=(const Base& other)
{
field1 = other.field1;
return *this;
}
};
class Derived : public Base {
public:
std::string field2;
Derived(std::string str1, std::string str2) : Base(str1), field2(str2) {}
virtual Derived& operator=(const Derived& other) override
{
Base::operator=(other);
field2 = other.field2;
return *this;
}
};
However, this gives a compiler error, because the Derived function is not actually overloading anything, the signatures are different.
Is it possible to override the operator= to write code like this?
Base* ptr = new Derived("old 1", "old 2");
Derived derived("new 1", "new 2");
*ptr = derived; // <- use the derived class operator= to assign both field1 and field2
This operator
virtual Derived& operator=(const Derived& other);
does not override the copy assignment operator declared in the base class.
You have to write
Derived& operator=(const Base& other) override;
That is the type of the parameter shall be const Base &.
Here is a demonstrative program.
#include <iostream>
struct A
{
virtual A & operator =( const A & )
{
std::cout << "A::operator =\n";
return *this;
}
};
struct B : A
{
virtual B & operator =( const A &a ) override
{
A::operator =( a );
std::cout << "B::operator =\n";
return *this;
}
};
int main()
{
B b1;
A &rb1 = b1;
B b2;
b2 = rb1;
return 0;
}
Its output is
A::operator =
B::operator =
Related
class Base{
public:
virtual Base& operator=( const Base& ) {
std::cout << "Base::operator=( const Base& )" << std::endl;
return *this;
}
};
class Derived: public Base{
public:
virtual Derived& operator=( const Base& ) {
std::cout << "Derived::operator=( const Base& )" << std::endl;
return *this;
}
virtual Derived& operator=( const Derived& ) {
std::cout << "Derived::operator=( const Derived& )" << std::endl;
return *this;
}
};
int main(){
Derived obj1, obj2;
Base& ba=obj2;
obj1=ba;//outputs[1]: Derived::operator=( const Base&)
Derived& de=obj2;
obj1=de;//outputs[2]: Derived::operator=( const Derived& )
Base& bb=obj2;
bb=obj1; //outputs[3]: Derived::operator=( const Base&)
}
I am confused on the case 3 output, shouldn't assignment operator take the right operand as the function argument, so that obj1 is taken as the argument and since obj1 is class type of Derived, so it will call Derived::operator=( const Derived& )?
For the output[3], why the output is Derived::operator=( const Base& ), not Derived::operator=( const Derived& )?
The Derived::operator=( const Derived& ) has no override candidate in the Base class. I suggest adding override keyword after each method which is intended to override base class behavior. It costs nothing and can make your code much safer.
If you want to the Derived::operator=( const Derived& ) to be called, you need to provide a correct method signature in the base class:
class Derived; // forward declaration
class Base{
public:
virtual Base& operator=( const Base& ) {
std::cout << "Base::operator=( const Base& )" << std::endl;
return *this;
}
virtual Base& operator=( const Derived& ) {
std::cout << "Base::operator=( const Derived& )" << std::endl;
return *this;
}
};
class Derived: public Base{
public:
virtual Derived& operator=( const Base& ) override {
std::cout << "Derived::operator=( const Base& )" << std::endl;
return *this;
}
virtual Derived& operator=( const Derived& ) override {
std::cout << "Derived::operator=( const Derived& )" << std::endl;
return *this;
}
};
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
Does the output of this example help clarify your question? You could always override the operator= in the derived class as follows:
#include <cstdio>
struct Base{
virtual ~Base() = default;
virtual void operator=(const Base&) {
std::printf("Base::=\n");
}
};
struct Derived: public Base {
void operator=(const Derived&) {
std::printf("Derived::=\n");
}
void operator=(const Base&) override{
std::printf("Derived::= Base\n");
}
};
int main() {
Base* a, *b;
Derived c,d;
a = &c;
b = &d;
*a = *b; //Dispatches the call to the derived class =
Base base;
Derived derived;
derived = base; //Usual case now after operator=(const Base&) in Derived
c = d; //Usual case
Base base1, base2;
base1 = base2; //Usual case
a = &base1;
b = &base2;
*a = *b; //Usual case
}
Output:
Derived::= Base
Derived::= Base
Derived::=
Base::=
Base::=
As is talked about in Item 33 in "More Effective C++", the assignment problem is
//Animal is a concrete class
Lizard:public Animal{};
Chicken:public Animal{};
Animal* pa=new Lizard('a');
Animal* pb=new Lizard('b');
*pa=*pb;//partial assignment
However, if I define Animal as an abstract base class, we can also compile and run the sentence:*pa=*pb. Partial assignment problem is still there.
See my example:
#include <iostream>
class Ab{ private: int a;
double b;
public:
virtual ~Ab()=0;
};
Ab::~Ab(){}
class C:public Ab{
private:
int a;
double b;
};
class D:public Ab{
private:
int a;
double b;
};
int main()
{
Ab *pc=new C();
Ab *pd=new D();
*pc=*pd;
return 0;
}
Do I miss something? Then what's the real meaning of the abstract base class?
I got the answer by myself. I missed a code snippet in the book.
Use protected operator= in the base class to avoid *pa=*pb. Use abstract base class to avoid animal1=animal2.Then the only allowed expressions are lizard1=lizard2;chicken1=chicken2;
See the code below:
#include <iostream>
class Ab{
private:
int a;
double b;
public:
virtual ~Ab()=0;
protected: //!!!!This is the point
Ab& operator=(const Ab&){...}
};
Ab::~Ab(){}
class C:public Ab{
public:
C& operator=(const C&){...}
private:
int a;
double b;
};
class D:public Ab{
public:
D& operator=(const D&){...}
private:
int a;
double b;
};
int main()
{
Ab *pc=new C();
Ab *pd=new D();
*pc=*pd;
return 0;
}
The abstract base class cannot help in case of assignment because the base sub-object is not instantiated (what an abstract class would block) but is sliced off the derived object (i.e. the assignment is done between already existing base sub-objects).
To avoid the problem the only solution I can think to is
make the assignment virtual
check in the assignment that the source instance is of the correct type
In code
#include <iostream>
struct Base {
int bx;
Base(int bx) : bx(bx) {}
virtual Base& operator=(const Base& other) {
bx = other.bx;
return *this;
}
};
struct A : Base {
int x;
A(int bx, int x) : Base(bx), x(x) {}
A& operator=(const Base& other) {
const A& other_a = dynamic_cast<const A&>(other);
Base::operator=(other);
x = other_a.x;
return *this;
}
};
struct B : Base {
int x;
B(int bx, int x) : Base(bx), x(x) {}
B& operator=(const Base& other) {
const B& other_b = dynamic_cast<const B&>(other);
Base::operator=(other);
x = other_b.x;
return *this;
}
};
The dynamic_cast<const A&>(other) is the operation that will fail if the object passed to the assignment operator is not of the correct derived type (it can be a sub-derived object, but this should be logically ok for an assignment source).
As an example:
int main(int argc, const char *argv[]) {
Base *pa1 = new A(1, 2);
Base *pa2 = new A(3, 4);
Base *pb1 = new B(5, 6);
Base *pb2 = new B(7, 8);
*pa1 = *pa2; std::cout << pa1->bx << "/" << dynamic_cast<A*>(pa1)->x << "\n";
*pb1 = *pb2; std::cout << pb1->bx << "/" << dynamic_cast<B*>(pb1)->x << "\n";
std::cout << "Ok so far\n";
*pa1 = *pb1; // Runtime error here (bad cast)
return 0;
}
It doesn't matter that your base class has pure virtual functions because you haven't defined the operator= for any of the classes. So when the compiler sees this statement:
*pc=*pd;
where pc and pd are both of type Ab, it will call the default assignment operator for Ab, which will result in partial assignment. As in the following example, I get the output as "Abstract Base" which is from abstract base class:
class A {
public:
virtual void foo() =0;
virtual A& operator=(const A& rhs) {
std::cout << "Abstract Base";
return *this;
}
};
class B : public A {
public:
virtual void foo() {
std::cout << "b:foo";
}
};
class C : public A {
public:
virtual void foo() {
std::cout << "c:foo";
}
};
int main()
{
A* b = new B();
A* c = new C();
*b = *c;
return 0;
}
Since you have not handled the assignment operators in your classes, you land up in situation partial assignment as Scot clearly describes in his article.
You need to handle assignments in your classes. In current design default implicit assignment operator of Ab is called and thus all the properties of children class are lost.
To avoid this you should have an implementation like this:
class Ab{ private: int a;
double b;
public:
virtual ~Ab()=0;
virtual Ab& operator=(const Ab& rhs){cout<<"in Ab="<<endl;}
};
Ab::~Ab(){}
class C:public Ab{
C& operator=(const Ab& rhs){cout<<"in C="<<endl;
return operator=(dynamic_cast<const C&>(rhs)); }
C& operator=(const C& rhs){
cout<<"do somethin in C="<<endl;
}
private:
int a;
double b;
};
class D:public Ab{
D& operator=(const Ab& rhs){cout<<"in D="<<endl;
return operator=(dynamic_cast<const D&>(rhs)); }
D& operator=(const D& rhs){
cout<<"do somethin in D="<<endl;
}
private:
int a;
double b;
};
I have following base class and derived class structure.
base/include/Base.h
namespace A
{
namespace B
{
class Base
{
public:
Base();
Base(const Type type, const Name name);
virtual ~Base();
// Copy constructor
Base(const Base& other);
// Assignment operator
Base& operator= (const Base& rhs);
// Comparison
bool operator< (const Base& rhs);
std::string toString();
//These enums have been defined in same file above the class
enum Type mType;
enum Name mName;
};
}
}
base/src/Base.cpp
#include "Base.h"
//and other required includes
namespace A
{
namespace B
{
Base::Base()
{
}
Base::Base(const Type type, const Name name)
{
}
Base::~Base()
{
}
// Copy constructor
Base::Base(const Base& other)
{
}
// Assignment operator
Base& Base::operator= (const Base& rhs)
{
}
// Comparison
bool Base::operator< (const Base& rhs)
{
}
std::string Base::toString()
{
}
}
}
base/test/test.h
#include "Base.h"
namespace A
{
namespace B
{
class Derived: public Base
{
public:
Derived();
Derived(const Type type, const Name name);
virtual ~Derived();
Derived(const Derived& other);
Derived& operator= (const Derived& rhs);
virtual std::string toString();
};
}
}
base/test/test.cpp
#include "test.h"
namespace A
{
namespace B
{
Derived::Derived()
{
}
Derived::Derived(const Type type, const Name name)
:Base(type, name)
{
}
Derived::~Derived()
{
}
Derived::Derived(const Derived& other)
{
}
Derived& Derived::operator= (const Derived& rhs)
{
}
std::string Derived::toString()
{
}
};
}
}
Now, I am building libBase.a for Base class directory.
And then I am trying to compile Derived class on command line like this in base/test directory:
g++ *.cpp -o Test \
-I /cygdrive/c/projects/base/include \
-L /cygdrive/c/projects/base/Build -lBase
But I get errors like:
/tmp/ccjLwXZp.o:Derived.cpp:(.text+0x10d): undefined reference to `A::B::Base::Base()
/tmp/ccjLwXZp.o:Derived.cpp:(.text+0x129): undefined reference to `A::B::Base::Base()
/tmp/ccjLwXZp.o:Derived.cpp:(.text+0x161): undefined reference to `A::B::base::Base(const Type type, const Name name)'
These errors I get for all the functions defined in base class.
I am not sure, what is it that I am doing wrong.
I have checked that libBase.a is in proper place
The problem might be related to the fact you've declared Type and Name enums after the c-tor declaration. Due to this the compiler can't build the Base class.
class Base
{
public:
enum Type mType; // <-- define the type and Name here, before
enum Name mName; // <-- the C-tor (actual usage)
Base();
Base(const Type type, const Name name);
virtual ~Base();
// Copy constructor
Base(const Base& other);
// Assignment operator
Base& operator= (const Base& rhs);
// Comparison
bool operator< (const Base& rhs);
std::string toString();
//These enums have been defined in same file above the class
//enum Type mType; // <<- your original location
//enum Name mName;
};
Is following pattern ok/safe ? Or are there any shortcomings ?
(I also use it for equality operators)
Derived& operator=(const Derived& rhs)
{
static_cast<Base&>(*this) = rhs;
// ... copy member variables of Derived
return *this;
}
This is fine, but it's a lot more readable IMHO to call the base-class by name:
Base::operator = (rhs);
Yes, it's safe.
A different syntax to do the same thing could be:
Base::operator=( rhs );
That's better to use
Base::operator=(rhs);
because if your base class have a pure virtual method the static_cast is not allowed.
class Base {
// Attribute
public:
virtual void f() = 0;
protected:
Base& operator(const Base&);
}
class Derived {
public:
virtual void f() {};
Derived& operator=(const Derived& src) {
Base::operator=(src); // work
static_cast<Base&>(*this) = src; // didn't work
}
}