因此,在理解应该如何解决遇到的这种多态性问题时,我遇到了一些问题。为了简短起见,让我们仅定义两个级别的类,一个父级和两个儿子:
parent :
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值,因此标准的operator =应该足够了,在实际项目中,这些类更为复杂,因此我确实需要自己实现一个,并且它们成功地调用了 parent 的。
所以现在,在我项目的某个地方,我有一个Entity *数组,它只能是son1或son2。我想遍历这个实体数组并将副本复制到另一个数组中。我希望能够编写的代码如下:
for (i = 0; i < entities; ++i) {
entityCopiesArray[i] = entitiesArray[i];
}
问题是,entityCopiesArray和entitysArray均为(Entity *)类型,因此当分配完成时,仅Entity.operator =()被调用,而在当前实体为son1的情况下,我需要同时拥有Entity.operator = ()和EntitySon1.operator =()调用。
我知道我可以将每个变量都强制转换为数组,以便调用正确的运算符,但是这需要额外的信息来说明哪些实体是son1,哪些实体是son2,并且还需要为每个可能的实体的son手动键入。
还有其他解决方法吗,还是我坚持这样做:
if (isSon1())
(EntitySon1)entityCopiesArray[i] = (EntitySon1)entitiesArray[i];
else if (isSon2())
(EntitySon2)entityCopiesArray[i] = (EntitySon2)entitiesArray[i];
// ... etc
编辑:我在深夜发表了这篇文章,并试图尽可能地压缩信息,所以我说的是不准确的。我显然有一个Entity *数组,否则我将无法使用多态。
最佳答案
考虑以下:
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;
这些情况都不是特别好。您的帖子听起来好像您正在为object slicing的严重情况做好准备。
不要做。
另一方面,如果要执行的操作是从列表中克隆对象:
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 << " 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;
}
现场演示:http://ideone.com/lrgJun
----编辑----
当您继承一个类时,您还将其数据成员也继承到您的整体结构中。在此示例中,Derived1的有效结构为:
struct Derived1 {
int a; // from Base
int d1Data[100];
};
我对
clone
的实现安静地依赖于复制构造函数,该复制构造函数实际上是将src的内存复制到dest,相当于memcpy(this, src, sizeof(*this));
。因此,您不需要将调用链接到clone
或类似的事情,魔术就是在复制构造函数中完成的。如果需要在混合中添加
Base
实例,我们可以在clone
中实现Base
成员-但请记住,添加到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;
...
};
此时,Derived1的实际内存结构如下所示:
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)
{
}
我对
clone
的Derived1
实现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
,并使用当前对象作为rhs
调用新的空克隆的copy-ctor并填充该克隆。关于c++ - C++对象多态性问题,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20295853/