简体   繁体   中英

C++ object polymorphism issue

So I'm having a little bit of problems understanding how I should fix this polymorphic issue I've ran into. To make things short, let's define only two levels of classes, a father and two sons:

The parent:

class Entity {
public:
    int x;

    Entity();
    ~Entity();
    Entity(const Entity&);
    Entity &operator=(const Entity&);
};

The two sons:

class EntitySon1 : public Entity {
public:
    int b;

    EntitySon1();
    ~EntitySon1();
    EntitySon1(const EntitySon1&);
    EntitySon1 &operator=(const EntitySon1&);
};

class EntitySon2 : public Entity {
public:
    int a;

    EntitySon2();
    ~EntitySon2();
    EntitySon2(const EntitySon2&);
    EntitySon2 &operator=(const EntitySon2&);
};

Please, forget the fact that in this example all classes only have an int value and the standard operator= should therefore be enough, in the real project these classes are more complex so I do have the need to implement one myself and they are succesfully calling parent ones as well.

So now, somewhere in my project, I have an array of Entity*, which can only be either son1 or son2. I want to go through this array of entities and make copies into another array. The code I'd like to be able to write is something like:

for (i = 0; i < entities; ++i) {
    entityCopiesArray[i] = entitiesArray[i];
}

Problem is, both entityCopiesArray and entitiesArray are of type (Entity*) so when the assign is done, only Entity.operator=() gets called, while I need, in the case that current Entity is son1, have both Entity.operator=() and EntitySon1.operator=() called.

I know I can cast each variable on the array so the right operator gets called, but that needs extra information to tell which entities are son1, which are son2, and also need to be typed manually for every possible Entity's son.

Is there any other way around this, or am I stuck with doing:

if (isSon1())
    (EntitySon1)entityCopiesArray[i] = (EntitySon1)entitiesArray[i];
else if (isSon2())
   (EntitySon2)entityCopiesArray[i] = (EntitySon2)entitiesArray[i];
// ... etc

edit: I made this post late night and tried to compact the information as much as possible so I said things that weren't accurate. I obviously have an array to Entity*, else I wouldn't be able to use polymorphism.

I have an array of Entities, which can only be either son1 or son2.

In general this is not possible. But you can have an array of Entity* , each of which points to a Son1 or a Son2 . Then you can have a virtual clone function such that a member of this family creates a copy of itself and returns a pointer (of type Entity* ) to it. Then you can copy the array quite easily.

Consider the following:

struct Base {
    int a;
};

struct Derived1 : public Base {
    int d1Data[100];
};

struct Derived2 : public Base {
    char d2Data[1500];
};

Now if we do the following:

Entity* e = new Entity;
Derived1* d1 = new Derived1;
Derived2* d2 = new Derived2;

std::cout << sizeof(*e) << ", " << sizeof(*d1) << ", " << sizeof(*d2) << '\n';

What will the output be? Hint: The numbers are not going to be the same.

So now what happens in each of the following cases?

*e = *(Entity*)d1;
*(Derived1*)e = *d1;
*(Derived2*)d1 = *d2;
*(Entity*)d1 = *(Entity*)(d2);
*(Derived1*)d2 = *d1;

None of these cases is particularly good. Your post makes it sound like you are setting yourself up for a bad case of object slicing .

DO NOT DO.

On the other hand, if what you are looking to do is to clone objects from a list:

std::vector<Base*> entities;
std::vector<Base*> copies;

entities.push_back(new Derived1);
entities.push_Back(new Derived2);

for (size_t i = 0; i < entities.size(); ++i) {
    Base* clone = make_a_copy_of(entities[i]);
}

then the way to do this is to add a member function to Base.

struct Base {
    int a;
    virtual Base* clone() const = 0;
};

struct Derived1 : public Base {
    int d1Data[100];
    Base* clone() const override {
        return new Derived1(*this);
    }
};

struct Derived2 : public Base {
    char d2Data[1500];
    Base* clone() const override {
        return new Derived2(*this);
    }
};

int main() {
    std::vector<Base*> entities;
    std::vector<Base*> copies;

    entities.push_back(new Derived1);
    entities.push_Back(new Derived2);

    for (size_t i = 0; i < entities.size(); ++i) {
        Base* clone = entities[i]->clone();
    }

    // remember to delete all the objects we allocated,
    // or wrap them with std::unique_ptr

    return 0;
}

I will probably be scowled at for using raw pointers like this without using something like std::unique_ptr to ensure the objects have a lifetime, so here is a complete version using unique_ptr. I'm not using make_unique because neither my GCC (4.8.2) or MSVC appear to support it.

#include <iostream>
#include <vector>
#include <memory>

struct Base {
    int m_a;
    Base(int a) : m_a(a) {}
    virtual ~Base() { std::cout << "Dtoring " << m_a << '\n'; }

    virtual std::unique_ptr<Base> clone() const = 0;
};

struct Derived1 : public Base {
    int d1Data[100];

    Derived1(int a) : Base(a) {}
    virtual ~Derived1() { std::cout << "D1 at " << (void*)this << " dtord\n"; }

    std::unique_ptr<Base> clone() const override { return std::unique_ptr<Derived1>(new Derived1 (*this)); }
};

struct Derived2 : public Base {
    char d2Data[10000];

    Derived2(int a) : Base(a) {}
    virtual ~Derived2() { std::cout << "D1 at " << (void*)this << " dtord\n"; }

    std::unique_ptr<Base> clone() const override { return std::unique_ptr<Derived2>(new Derived2 (*this)); }
};

int main()
{
    std::vector<std::unique_ptr<Base>> entities;
    {
        std::vector<std::unique_ptr<Base>> copies;

        entities.emplace_back(new Derived1 (3));
        entities.emplace_back(new Derived2 (5));

        for (auto& ent : entities) {
            copies.emplace_back(ent->clone());
        }

        std::cout << "copies going out of scope\n";
    }

    std::cout << "entities going out of scope\n";

    return 0;
}

Live demo: http://ideone.com/lrgJun

---- EDIT ----

When you inherit a class, you also inherit it's data members into your overall structure. In this example, the effective structure of Derived1 is:

struct Derived1 {
    int a; // from Base
    int d1Data[100];
};

My implementation of clone is quietly relying on the copy constructor which is essentially doing a memory copy of src to dest, the equivalent of memcpy(this, src, sizeof(*this)); . Because of this you don't need to chain calls to clone or anything like that, the magic is done in the copy constructor.

If you need to add instances of Base into your mix, we can implement the clone member in Base - but bear in mind that any "special" members you add to Base are going to be inherited in all the derived classes too.

I'll convolute the classes a little and show you what the copy constructors for Base and Derived1 effectively look like:

struct Base {
    int m_int;
    double m_double;
    std::string m_name;
private:
    unsigned int m_baseOnly;
    ...
};

struct Derived1 : public Base {
    // inherited m_int, m_double and m_name
    // also inherited m_baseOnly, we just can't access it.
    std::array<int, 100> m_data;
    std::string m_title;
    std::shared_ptr<Base> m_buddy;
    ...
};

At this point, the actual in-memory structure of a Derived1 looks like this:

Derived1 {
    int m_int;
    double m_double;
    std::string m_name;
    unsigned int m_baseOnly;
    std::array<int, 100> m_data;
    std::string m_title;
    std::shared_ptr<Base> m_buddy;
};

Given these definitions, unless we implement our own copy constructor or disable copy construction, this is effectively what the compiler will generate for us:

Base::Base(const Base& rhs) // Base copy constructor
    : m_int(rhs.m_int)
    , m_double(rhs.m_double)
    , m_name(rhs.m_name)
    , m_baseOnly(rhs.m_baseOnly)
{
}

Derived1::Derived1(const Derived1& rhs)
    : Base(rhs) // copy-construct the Base portion
    , m_data(rhs.m_data) // hence why I used std::array
    , m_title(rhs.m_title)
    , m_buddy(rhs.m_buddy)
{
}

My implementation of clone for Derived1

std::unique_ptr<Base> clone() const override
{
    return std::unique_ptr<Derived1>(new Derived1 (*this));
}

or

std::unique_ptr<Base> clone() const override
{
    const Derived1& rhs = *this; // Reference to current object.
    Derived1* newClone = new Derived1(rhs);
    return std::unique_ptr<Derived1>(newClone);
}

which is creating a new Derived1 , invoking the new, empty, clone's copy-ctor with the current object as rhs and filling out the clone.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM