Search code examples
c++listoperator-overloadingderived-classassignment-operator

c++ assignment operator= overloading with derived classes


I'm currently writing a complicated class and in it I basically need to copy a list of derived classes. The simplified version is, as follows: I have a base class from which I derive several other classes:

class Base
{
public:
    virtual void test(void)
    {
        cout << "Base" << endl;
    }
    Base(vector<Base*> *pointer)
    {
        pointer->push_back(this);
    }
    virtual Base& operator=(const Base& rhs)
    {
        cout << "Base=" << endl;
        return *this;
    }
};
class A : public Base
{
public:
    void test(void)
    {
        cout << "A" << endl;
    }
    A(vector<Base*> *pointer) : Base(pointer) {}
    A& operator=(const A& rhs)
    {
        cout << "A=" << endl;
        return *this;
    }
};
class B : public Base
{
public:
    void test(void)
    {
        cout << "B" << endl;
    }
    B(vector<Base*> *pointer) : Base(pointer) {}
    B& operator=(const B& rhs)
    {
        cout << "B=" << endl;
        return *this;
    }
};

Then I create a list of objects, which I save in the in a pointer list of the Base class:

vector<Base*> listA;

new Base(&listA);
new A(&listA);
new B(&listA);

These objects I then want to copy in a second list with the same classes (same order), but which might have different values.

for (int i = 0; i < (int)listA.size(); i++)
{
    (*listA[i]) = (*listB[i]);
}

However c++ is not able to do that. Because the list has the type Base*, dereferencing creates an object of type Base. Therefore the assignment operator= of the Base class is called instead of the correct one from the derived class. How can I fix this?

Or how can I tell c++ to use the right operator? Maybe by some isinstanceof-function?

For a full sample see:

int main()
{
    vector<Base*> listA;

    new Base(&listA);
    new A(&listA);
    new B(&listA);

    vector<Base*> listB;

    new Base(&listB);
    new A(&listB);
    new B(&listB);


    for (int i = 0; i < (int)listA.size(); i++)
    {
        (*listA[i]).test();
    }
    for (int i = 0; i < (int)listA.size(); i++)
    {
        (*listA[i]) = (*listB[i]);
    }
}

Which outputs:

Base
A
B
Base=
Base=
Base=

Solution

  • There are a few misunderstandings here. First and foremost, what does it mean to assign an instance of a derived class to an instance of a base class? Let's take a simple hierarchy:

    struct A { int x; };
    struct B : A { int y; };
    
    A a;
    B b;
    a = b; // what should this do?
    b = a; // what about this?
    

    With normal C++, the first one does object slicing, and the second one is ill-formed. But even the first one, well well-formed, is typically not what you want to do anyway. Are you sure you want to be slicing?


    The second is that while you made your assignment operator virtual:

    virtual Base& operator=(const Base& rhs)
    

    None of the derived classes actually override it. A's assignment operator takes an A const& and B's takes a B const&. If you marked the two with override, your compiler would point this out to you. If you fix those two to take a Base const& argument, then you would get what you want printed - but it's probably still not what you actually want to have happen.


    In order to actually make polymorphic copies, a typical solution is to provide a virtual clone method:

    virtual Base* clone() const = 0;
    

    That your derived classes implement:

    struct A : Base {
        A* clone() const override { return new A(*this); }
    };
    

    And then use clone() instead of assignment. There will be no slicing here.


    Insert the usual caveats about memory management and raw pointers here.