单个向量中不同类的对象?

时间:2011-06-08 04:12:56

标签: c++ class vector

在我的代码中,我有一组对象:

class Sphere { ...
class Plane { ...
...

我需要在vector中使用它们的集合(它们都有不同的类型)。如何将不同类的对象添加到vector

7 个答案:

答案 0 :(得分:8)

球体和平面需要一个共同的基础类型,或者你的矢量需要由void*组成。

共同基础类型(更好):

class Shape { ... };
class Sphere : public Shape { ... };
class Plane : public Shape { ... };

std::vector<Shape*> shapes;

void*(不太好):

std::vector<void*> shapes;

答案 1 :(得分:7)

这些类需要有一个共同的基类,例如:

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*,或者找到不同的方法来执行此操作。

答案 2 :(得分:5)

创建多态类型的容器是一种经典的解决方案,它有自己的问题。其中一种类型必须变成多态才能将它们添加到容器中 - 这不是一个好理由。另一个问题是早期和紧密耦合导致更难以维护和缺乏灵活性,只是为了将它们添加到容器中 - 这不是一个好理由。幸运的是,在C ++中有更好的选择。

更好的解决方案是在容器中存储函数而不是对象本身。您希望将不同类型放在同一容器中的常见原因是对所有这些类型执行相同的操作,例如Sphere::Draw()Plane::Draw()。你可以做的是创建一个绘图函数的容器而不是擦除类型。 E.g。

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()

答案 3 :(得分:1)

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 ++将允许它工作

答案 4 :(得分:0)

对象是否以一种美妙的方式相关?如果他们不是,那么你可能不应该。

如果是,您需要在inheritance上阅读。

答案 5 :(得分:0)

其他帖子告诉了你大部分你需要知道的事情。我想补充一点,boost有指针容器可能很方便,因为它们在被销毁时清理那些内容。 Boost manual

答案 6 :(得分:0)

如果使用的是C ++ 17,则使用std::variant是最佳解决方案。如果没有,让我解释一下:

由于较小的高速缓存未命中,值的向量原则上比指针的向量快。我研究了此解决方案,这是基本思想。假设您有三种类型ParentChild1Child2。它们的大小例如是32字节,40字节和48字节。如果创建std::vector<char[48]>,原则上您将可以保存其中的任何值。而且由于Child1和Child2继承自Base,因此您可以通过Base*访问它们,并利用每个类中已经存在的vtable多态调用Child1Child2中的方法

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

要使用它,您需要指定基类和内部内存块的大小作为模板参数,以足以适合您打算放入的任何类。

以下是ParentChild1Child2的示例:

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