Search code examples
c++assignment-operator

What happens if I "slice" to an abstract class


First off, I know the assignment operator cannot be defined in a class that has some subclasses. I understand it is because we don't want to make Subclass1 = Subclass2 possible.

But let's assume Class is an abstract class and Subclass is its... ya know. Then, is it feasible to do something like this?

Class* p = new Subclass;
Subclass s1;
*p = s1;

Actually, I tried implementing that in my code, but it didn't work :) Could you please help?

My full code:

#include <cstdlib>
#include <iostream>
#include <typeinfo>

using namespace std;

class BadIndex{
    int index;
public:
    BadIndex(int i):index(i){}
    int getIndex(){ return index; }
};

template <typename t>
class Wielomian{
public:
    ~Wielomian(){}
    virtual int getDeg() = 0;
    virtual t& operator [](int) = 0;
    virtual bool operator ==(Wielomian<t>&) = 0;
    virtual Wielomian<t>& operator +(Wielomian<t>&) = 0;
    virtual Wielomian<t>& operator +=(Wielomian<t>&) = 0;
};

template <typename t>
class TabWiel: public Wielomian<t>{
    int deg;
    t* plnml;
public:
    TabWiel(t tab[] = {}, int size = 0);
    ~TabWiel();
    TabWiel(const TabWiel<t>&);
    TabWiel<t>& operator =(const TabWiel<t>&);

    template <typename st>
    friend ostream& operator <<(ostream& s, TabWiel<st>& tw);                                             
    int getDeg(){ return deg; }
    t& operator [](int);
    bool operator ==(Wielomian<t>&);
    TabWiel<t>& operator +(Wielomian<t>&);
    TabWiel<t>& operator +=(Wielomian<t>&);
};

template <typename t>
TabWiel<t>& TabWiel<t>::operator =(const TabWiel<t>& tw){
    if (this != &tw){
        delete[] plnml;
        deg = tw.deg;
        plnml = new t[deg + 1];
        for (int i = 0; i < deg + 1; i++)
            plnml[i] = tw.plnml[i];
    }
    return *this;
}

template <typename t>
TabWiel<t>::TabWiel(t tab[], int size){
    deg = size - 1;
    plnml = new t[deg + 1];
    for (int i = 0; i < deg + 1; i++)
        plnml[i] = tab[i];
    if (deg == -1){
        deg = 0;
        plnml[0] = 0;
    }
}

template <typename t>
TabWiel<t>::~TabWiel(){
    delete[] plnml;
}

template <typename t>
TabWiel<t>::TabWiel(const TabWiel<t>& tw){
    deg = tw.deg;
    plnml = new t[deg + 1];
    for (int i = 0; i < deg + 1; i++)
        plnml[i] = tw.plnml[i];
}

template <typename t>
t& TabWiel<t>::operator [](int s){
    if (s >= 0 && s < deg + 1)
        return plnml[s];
    else
        throw BadIndex(s);
}

template <typename t>
bool TabWiel<t>::operator ==(Wielomian<t>& tw){
    try{
        TabWiel<t>& rhs = dynamic_cast<TabWiel<t>&>(tw);
        if (deg == rhs.deg){
            for (int i = 0; i < deg + 1; i++){
                if (plnml[i] != rhs.plnml[i])
                    return false;
            }
            return true;
        }
        return false;
    }
    catch (const bad_cast& e){
        cerr << "An exception" << e.what() << " thrown." << endl;
    }
}

template <typename t>
ostream& operator <<(ostream& s, TabWiel<t>& tw){
    for (int i = 0; i < tw.deg + 1; i++){
        if (i != tw.deg)
            s << tw.plnml[i] << "x^" << i << "+";
        else
            s << tw.plnml[i] << "x^" << i << endl;
    }
    return s;
}

template <typename t>
TabWiel<t>& TabWiel<t>::operator +(Wielomian<t>& tw){
    try{
        TabWiel<t>& rhs = dynamic_cast<TabWiel<t>&>(tw);
        if (rhs.deg <= deg){
            for (int i = 0; i < rhs.deg + 1; i++)
                plnml[i] = plnml[i] + rhs.plnml[i];
            return *this;
        }
        else{
            t* tmp = new t[deg + 1];
            for (int i = 0; i < deg + 1; i++)
                tmp[i] = plnml[i];
            int tmp_deg = deg;
            delete[] plnml;
            deg = rhs.deg;
            plnml = new t[deg + 1];
            for (int i = 0; i < deg + 1; i++){
                if(i < tmp_deg + 1)
                    plnml[i] = tmp[i] + rhs.plnml[i];
                else
                    plnml[i] = rhs.plnml[i];
            }
            return *this;
        }
    }
    catch (const bad_cast& e){
        cerr << "An exception" << e.what() << " thrown." << endl;
    }
}

template <typename t>
TabWiel<t>& TabWiel<t>::operator +=(Wielomian<t>& tw){
    try{
        TabWiel<t>& rhs = dynamic_cast<TabWiel<t>&>(tw);
        TabWiel<t>* nowy = new TabWiel<t>;
        TabWiel<t> copy;
        copy = *this;
        *nowy = copy + rhs;
        return *nowy;
    }
    catch (const bad_cast& e){
        cerr << "An exception" << e.what() << " thrown." << endl;
    }
}

I wish the assignment of *p to non-empty subclass object worked. But it doesn't - all the code does, is that it enters "Wielomian" definition and then proceeds to the next line of the main function (which in my case is the last line).


Solution

  • Your question is very interesting.

    First of all, your code doesn't work because of slicing: you have two objects of Subclass, but the compiler thinks that one of it is only a Class. So the code generated copies only the common part of the data.

    To demonstrate this, let's ellaborate on gha.st 's initial code extract:

    struct Class { int a; virtual void hugo() = 0; };
    struct Subclass : Class { int b; void hugo() override { cout<<"sub"<<a<<b<<endl; } };
    int main() {
        Class* p = new Subclass;
        static_cast<Subclass*>(p)->a = 2; 
        static_cast<Subclass*>(p)->b = 3; 
        Subclass s1;
        s1.a = 4; s1.b=5;
        *p = s1;  // slicing !!  
        p->hugo();
        return 0;
    }  
    

    What happens here ? Well, b member isn't copied, although *p is in reality a Subclass !

    But *p is still a Subclass, so we could use polymorphism to get this work. The trick is to use an virtual clone() member to clone an object (the object shall know its own type) into a target, if the target has the same type.
    Then you could define operator=() for Class to use this clone(). This makes it handy to use, but the drawback is that you'll no longer be able to rely on default operator= for any descendent of Class if you want to avoid an endless recursion.

    Here the proof of concept:

    struct Class {
        int a; 
        virtual void hugo() = 0; 
        virtual bool clone(Class*t) = 0; 
        Class& operator=(Class& o) {
            if (!o.clone(this)) {  // try to clone on subclass on a target of same subclass
                // here,the source and target might differ. Only common members can be copied
                a = o.a; 
            }
            return *this; 
        }
    };
    struct Subclass : Class {
        int a,b; 
        void hugo() override { cout<<"sub"<<a<<b<<endl; } 
        bool clone(Class*t) { 
            cout<<"Clone "; 
            if (dynamic_cast<Subclass*>(t)) {  // if source and target of same subclass
                 //*dynamic_cast<Subclass*>(t) = *this;  // this doesn't work cause default operator will try to copy the Class base, causing recursion
                 dynamic_cast<Subclass*>(t)->a = a;   // copy members
                 dynamic_cast<Subclass*>(t)->b = b;
                 return true; 
            }
            else return false; // or tell that class of source and target are different. 
        } 
    };
    

    Then you can use the main() function above, and see that the object is properly copied.

    This trick is a kind of simplified double dispatch. You could even elaborate further by foreseing various kind of conversions depending on source and target subtype.