C++对象多态性问题



所以我在理解如何解决我遇到的这个多态问题时遇到了一些问题。简而言之,让我们只定义两个级别的类,一个父亲和两个儿子:

父级:

class Entity {
public:
    int x;
    Entity();
    ~Entity();
    Entity(const Entity&);
    Entity &operator=(const Entity&);
};

两个儿子:

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&);
};

请忘记这样一个事实,即在这个例子中,所有类只有一个 int 值,因此标准运算符=应该就足够了,在实际项目中,这些类更复杂,所以我确实需要自己实现一个,并且它们也成功地调用了父类。

所以现在,在我的项目中的某个地方,我有一个实体*数组,它只能是 son1 或 son2。我想遍历这个实体数组并将副本复制到另一个数组中。我希望能够编写的代码是这样的:

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

问题是,entityCopyiesArray 和 entityArray 都是 (Entity*( 类型,所以当赋值完成时,只调用 Entity.operator=((,而我需要,在当前实体是 son1 的情况下,同时调用 Entity.operator=(( 和 EntitySon1.operator=((。

我知道我可以在数组上强制转换每个变量,以便调用正确的运算符,但这需要额外的信息来判断哪些实体是 son1,哪些是 son2,并且还需要为每个可能的实体的儿子手动键入。

有没有其他方法可以解决这个问题,或者我是否坚持这样做:

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

编辑:我在深夜发表了这篇文章,并试图尽可能压缩信息,所以我说了一些不准确的话。我显然有一个实体*的数组,否则我将无法使用多态性。

请考虑以下事项:

struct Base {
    int a;
};
struct Derived1 : public Base {
    int d1Data[100];
};
struct Derived2 : public Base {
    char d2Data[1500];
};

现在,如果我们执行以下操作:

Entity* e = new Entity;
Derived1* d1 = new Derived1;
Derived2* d2 = new Derived2;
std::cout << sizeof(*e) << ", " << sizeof(*d1) << ", " << sizeof(*d2) << 'n';

输出将是什么?提示:数字不会相同。

那么现在在以下每种情况下会发生什么?

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

这些案例都不是特别好。你的帖子听起来像是在为一个糟糕的对象切片情况做准备。

不要做。

另一方面,如果您要做的是从列表中克隆对象:

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]);
}

那么这样做的方法是向 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;
}

我可能会因为使用这样的原始指针而不使用 std::unique_ptr 之类的东西来确保对象具有生命周期而皱眉,所以这里有一个使用 unique_ptr 的完整版本。我没有使用make_unique因为我的 GCC (4.8.2( 和 MSVC 似乎都不支持它。

#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 << " dtordn"; }
    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 << " dtordn"; }
    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 scopen";
    }
    std::cout << "entities going out of scopen";
    return 0;
}

现场演示:http://ideone.com/lrgJun

----编辑----

继承类时,也会将其数据成员继承到整体结构中。在此示例中,Derived1 的有效结构为:

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

我对clone的实现悄悄地依赖于复制构造函数,该构造函数本质上是将 src 的内存副本执行到 dest,相当于 memcpy(this, src, sizeof(*this)); .因此,您不需要将调用链接到clone或类似的东西,魔术是在复制构造函数中完成的。

如果您需要将Base实例添加到组合中,我们可以在 Base 中实现 clone 成员 - 但请记住,您添加到Base中的任何"特殊">成员也将在所有派生类中继承。

我将稍微讨论一下这些类,并向您展示 Base 和 Derived1 的复制构造函数实际上是什么样的:

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;
    ...
};

此时,Deferrd1 的实际内存中结构如下所示:

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;
};

给定这些定义,除非我们实现自己的复制构造函数或禁用复制构造,否则编译器实际上将为我们生成:

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)
{
}

我为Derived1实现clone

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

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);
}

这是创建一个新的Derived1,调用新的、空的克隆的复制 ctor rhs当前对象并填写克隆。

我有一个实体数组,它只能是 son1 或 son2。

一般来说,这是不可能的。但是你可以有一个数组 Entity* ,每个数组都指向一个Son1或一个Son2。然后,您可以拥有一个虚拟clone函数,以便此族的成员创建自身的副本并返回指向它的指针(类型为 Entity*(。然后,您可以非常轻松地复制数组。

最新更新