【问题标题】:Objects of different classes in a single vector?单个向量中的不同类的对象?
【发布时间】:2011-09-10 13:48:21
【问题描述】:

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

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

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

【问题讨论】:

标签: c++ class vector


【解决方案1】:

这些对象是否以一种有意义的方式相关?如果不是,那么您可能不应该这样做。

如果是,您将需要阅读 inheritance

【讨论】:

    【解决方案2】:

    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*
    • 我正在使用箭头运算符(-&gt; 的东西)来调用函数,它似乎有点工作。我现在得到了更好的错误,所以谢谢!
    • 如果您想查看导致错误的代码,我已将其编辑到我的问题中。我不知道它有什么问题......
    • 其实,没关系。我用Sphere* 替换了Object*,一切正常。感谢您的回答!
    【解决方案3】:

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

    class MyBase { };
    class Sphere : public MyBase { };
    class Plane : public MyBase { };
    

    然后为了将多态对象存储在向量中,您必须存储指向它们的指针(因为它们的大小可能与基类不同)。我建议使用 std::shared_ptr&lt;MyBase&gt;std::unique_ptr&lt;MyBase&gt;(如果 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/…
    • 这是这个问题的链接。但根据猜测,我会说问题在于您没有使用指针或引用。您必须始终使用多态类型的指针或引用。
    • 这是一个正确的:stackoverflow.com/questions/6271665/…
    • 那么如果我要从vector 引用一个对象,我将如何初始化它?如果我这样做Object target = *objects[i];,我将无法使用非基类的函数。
    • 您必须始终使用引用或指针来引用多态类型。如果您执行Object target = *object[i];,您的对象将被复制到Object 并丢失其原始类型。您可以使用Object &amp;target = *object[i];Object *target = object[i];,这将允许您使用虚函数。要获取特定类型,请使用Sphere *target = dynamic_cast&lt;Sphere*&gt;(objects[i]);。如果对象不是球体,dynamic_cast 将返回 NULL。
    【解决方案4】:
    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++ 将允许这样做

    【讨论】:

    • 哇,我的代码看起来与此非常相似。现在,我正在努力创建新对象和定义属性。我当前的代码块如下所示:pastebin.com/raw.php?i=1KCMpP87
    • 让我猜猜(我猜是双精度类型)半径是在球体中声明的,而不是在对象中声明的?检查确定,因为Object light仍然是一个类Object,它恰好指向了新分配的Sphere的内存,不能直接访问radius,除非你没有把radius放在保留字“protected:”下,那么在您这样做之前,您应该无法访问它。
    • 所以我必须在 Object 主类中定义所有特定于对象的变量?
    • 是的,但是如果您希望派生类可以访问它,请记住将 radius 放在“protected:”关键字下。请记住,继承与组合有其优缺点artima.com/designtechniques/compoinhP.html
    【解决方案5】:

    其他帖子已告诉您大部分您需要知道的内容。我想补充一点,boost 有指针容器,当它们被销毁时它们会清理那里的内容时可能会很方便。 Boost manual

    【讨论】:

      【解决方案6】:

      创建多态类型的容器是一种经典的解决方案,但也有其自身的问题。其中一种类型必须变为多态才能将它们添加到容器中——这不是一个好的理由。另一个问题是过早的紧耦合导致更困难的维护和缺乏灵活性,只是为了将它们添加到容器中——这不是一个好的理由。幸运的是,在 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()

      【讨论】:

      • 树倒在树林里的那一刻!
      【解决方案7】:

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

      值向量原则上比指针向量更快,因为缓存未命中率较小。我研究了这个解决方案,这是基本思想。假设你有ParentChild1Child2 三种类型。它们的大小例如是 32 字节、40 字节和 48 字节。如果您创建std::vector&lt;char[48]&gt;,原则上您将能够保存其中的任何值。由于 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();
      

      【讨论】:

        猜你喜欢
        • 2022-01-15
        • 2011-03-29
        • 2015-07-13
        • 2015-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-16
        • 1970-01-01
        相关资源
        最近更新 更多