【发布时间】:2011-09-10 13:48:21
【问题描述】:
在我的代码中,我有一组对象:
class Sphere { ...
class Plane { ...
...
我需要在vector 中使用它们的集合(它们都有不同的类型)。如何将不同类的对象添加到vector?
【问题讨论】:
在我的代码中,我有一组对象:
class Sphere { ...
class Plane { ...
...
我需要在vector 中使用它们的集合(它们都有不同的类型)。如何将不同类的对象添加到vector?
【问题讨论】:
这些对象是否以一种有意义的方式相关?如果不是,那么您可能不应该这样做。
如果是,您将需要阅读 inheritance。
【讨论】:
Sphere 和 Plane 需要一个共同的基本类型,或者您的向量需要由 void*'s 组成。
通用基础类型(更好):
class Shape { ... };
class Sphere : public Shape { ... };
class Plane : public Shape { ... };
std::vector<Shape*> shapes;
或void*的(不太好):
std::vector<void*> shapes;
【讨论】:
vector 声明一个像Sphere 这样的对象时,它是Shape 类型还是Sphere 类型?我问是因为vector 是我迭代的场景描述对象,而基类声明(Shape foo = *objects[i];)用我在基类。我很难解释,因为我不会用 C++ 编写代码......
Shape*。如果您只需要调用 Shape 类中的非虚拟方法,或者首先在 Shape 中声明的虚拟方法,那么您就完成了。如果需要调用子类中的非虚方法,或者访问子类中的公共字段,则必须在使用前将检索到的Shape* 强制转换为Sphere*。
-> 的东西)来调用函数,它似乎有点工作。我现在得到了更好的错误,所以谢谢!
Sphere* 替换了Object*,一切正常。感谢您的回答!
这些类需要有一个共同的基类,例如:
class MyBase { };
class Sphere : public MyBase { };
class Plane : public MyBase { };
然后为了将多态对象存储在向量中,您必须存储指向它们的指针(因为它们的大小可能与基类不同)。我建议使用 std::shared_ptr<MyBase> 或 std::unique_ptr<MyBase>(如果 C++0x 不可用,则使用 Boost)。
std::vector<std::shared_ptr<MyBase> > v;
v.push_back<std::shared_ptr<MyBase>(new Sphere());
v.push_back<std::shared_ptr<MyBase>(new Plane());
如果没有共同的基础,您必须使用void*,或者找到其他方法来执行此操作。
【讨论】:
Object 类设置,但它用类中的默认方法覆盖了我的类特定方法。这是我之前的问题:stackoverflow.com/questions/6274136/…
vector 引用一个对象,我将如何初始化它?如果我这样做Object target = *objects[i];,我将无法使用非基类的函数。
Object target = *object[i];,您的对象将被复制到Object 并丢失其原始类型。您可以使用Object &target = *object[i]; 或Object *target = object[i];,这将允许您使用虚函数。要获取特定类型,请使用Sphere *target = dynamic_cast<Sphere*>(objects[i]);。如果对象不是球体,dynamic_cast 将返回 NULL。
Class Shape{...code...}
Class Sphere : public Shape{...code...}
Class Plane : public Shape{...code...}
std::vector<Shape*> List;
List.push_back(new Sphere);
List.push_back(new Plane);
或
//Base class to derived class
Shape* Shape_Sphere = new Sphere();
Shape* Shape_Plane = new Plane();
std::vector<Shape*> List;
List.push_back(Shape_Sphere);
List.push_back(Shape_Plane);
如果你想删除指针
std::vector<Shape*>::iterator it;
for(it = List.begin(); it != List.end(); ++it)
{
delete *it;
}
由于 Shape 和 Sphere/Plane 的向量存储实例是从基类 Shape 派生的,C++ 将允许这样做
【讨论】:
Object 主类中定义所有特定于对象的变量?
其他帖子已告诉您大部分您需要知道的内容。我想补充一点,boost 有指针容器,当它们被销毁时它们会清理那里的内容时可能会很方便。 Boost manual
【讨论】:
创建多态类型的容器是一种经典的解决方案,但也有其自身的问题。其中一种类型必须变为多态才能将它们添加到容器中——这不是一个好的理由。另一个问题是过早的紧耦合导致更困难的维护和缺乏灵活性,只是为了将它们添加到容器中——这不是一个好的理由。幸运的是,在 C++ 中有更好的选择。
更好的解决方案是将函数而不是对象本身存储在容器中。要将不同类型放在同一个容器中的常见原因是对所有类型执行相同的操作,例如Sphere::Draw() 或Plane::Draw()。您可以做的是创建一个绘图函数容器并擦除类型。例如
vector<function<void()>> drawings;
Sphere s;
Plane p;
drawings.push_back(bind(s, &Sphere::Draw));
drawings.push_back(bind(p, &Plane::Draw));
for(auto I = drawings.begin(); I != drawings.end(); ++i) (*i)();
通过这样做,您避免了强耦合和其他继承问题,并获得了更灵活、更通用的解决方案。
上述解决方案仅适用于 C++11,因为它需要 std::function()
【讨论】:
如果您使用 C++17,则使用 std::variant 将是最佳解决方案。如果不是,让我解释一下:
值向量原则上比指针向量更快,因为缓存未命中率较小。我研究了这个解决方案,这是基本思想。假设你有Parent、Child1 和Child2 三种类型。它们的大小例如是 32 字节、40 字节和 48 字节。如果您创建std::vector<char[48]>,原则上您将能够保存其中的任何值。由于 Child1 和 Child2 继承自 Base,因此您可以通过 Base* 访问它们,并利用每个类中已经存在的 vtable 来多态调用 Child1 和 Child2 中的方法。
我为std::vector 创建了一个包装器来做到这一点。在这里
template <typename Parent, typename... Children>
class polymorphic_vector {
private:
template <typename Base, typename... Others>
class alignas(16) polymorphic {
private:
static constexpr size_t round_to_closest_16(size_t size) {
return ((size % 16) == 0) ? size : ((size / 16) + 1) * 16;
}
template <typename T>
static constexpr size_t get_max_type_size() {
return sizeof(T);
}
template <typename T, typename Arg, typename... Args>
static constexpr size_t get_max_type_size() {
return max(sizeof(T), get_max_type_size<Arg, Args...>());
}
static constexpr size_t max(size_t v1, size_t v2) {
return v1 > v2 ? v1 : v2;
}
class wrapper {
public:
static constexpr int m_size = get_max_type_size<Others...>();
char m_data[m_size];
};
public:
wrapper m_wrapper;
};
using pointer_diff_t = int16_t;
std::vector<polymorphic<Parent, Children...>> m_vector;
std::vector<pointer_diff_t> m_pointer_diff;
template <typename BaseAddr, typename ModifiedAddr>
pointer_diff_t get_pointer_diff(BaseAddr base, ModifiedAddr modified) {
char* base_p = reinterpret_cast<char*>(base);
char* modified_p = reinterpret_cast<char*>(modified);
return base_p - modified_p;
}
template <typename BaseAddr, typename ModifiedAddr>
ModifiedAddr get_modified_addr(BaseAddr base, pointer_diff_t diff) {
char* base_p = static_cast<char*>(base);
return reinterpret_cast<ModifiedAddr>(base_p - diff);
}
public:
polymorphic_vector(int size) : m_vector(size), m_pointer_diff(size) {}
polymorphic_vector() : m_vector(), m_pointer_diff() {}
Parent* get(int index) {
return get_modified_addr<char*, Parent*>(
m_vector[index].m_wrapper.m_data, m_pointer_diff[index]);
}
template <typename Q>
void push_back(const Q& q) {
static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
static_assert(std::is_base_of<Parent, Q>::value);
m_vector.emplace_back();
::new (m_vector.back().m_wrapper.m_data) Q(q);
m_pointer_diff.emplace_back(get_pointer_diff(
m_vector.back().m_wrapper.m_data,
static_cast<Parent*>(
reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
}
template <typename Q, typename... Args>
void emplace_back(const Args&... args) {
static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
static_assert(std::is_base_of<Parent, Q>::value);
m_vector.emplace_back();
::new (m_vector.back().m_wrapper.m_data) Q(args...);
m_pointer_diff.emplace_back(get_pointer_diff(
m_vector.back().m_wrapper.m_data,
static_cast<Parent*>(
reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
}
void shuffle() {
std::vector<int> indexes(m_vector.size());
std::iota(indexes.begin(), indexes.end(), 0);
for (int i = 0; i < m_vector.size(); i++) {
std::swap(m_pointer_diff[i], m_pointer_diff[indexes[i]]);
std::swap(m_vector[i], m_vector[indexes[i]]);
}
}
void reserve(int size) { m_vector.reserve(size); }
};
要使用它,您需要将基类和内部内存块的大小指定为模板参数,该大小足以容纳您计划放入的任何类。
这是Parent、Child1 和Child2 的示例:
std::vector<Parent, 48> v;
v.emplace_back<Parent>();
v.emplace_back<Child1>(param1, param2);
v.emplace_back<Child2>(param1);
v.get(0)->method1();
v.get(1)->method1();
【讨论】: