【问题标题】:Use of a list of pointers in C++, (inheritance or performance?)在 C++ 中使用指针列表(继承还是性能?)
【发布时间】:2013-11-05 20:32:36
【问题描述】:

我得到了一些代码来阅读,这些代码对网格进行了一些几何操作。

根据定义,网格数据结构应至少包含以下信息 关于点的坐标、边缘连通性和人脸信息。

所以,给我的代码有定义顶点、边和面数据结构的类, 分别命名为顶点、边和面。

但是网格类看起来像这样。

class basemesh
{
public:
  /* Methods to operate on the protected data below.*/

protected:
   /*! list of edges */
  std::list<Edge*>           m_edges;

  /*! list of vertices */
  std::list<Vertex*>         m_verts;

  /*! list of faces */
  std::list<Face*>       m_faces;

}

我的问题:为什么网格数据结构存储指针列表而不是 相应对象本身的列表。

例如为什么不直接说std::list&lt;Vertex&gt;

我已经看到这个结构被用于其他几个 C++ 代码

这与类的继承有关吗?还是有事要做 关于迭代列表的性能?

顾名思义,这个 basemesh 类是一个基类, 衍生出其他专门的网格。

【问题讨论】:

  • 简单的指针比副本快。他们指向,而不是复制。
  • 难道边、顶点和面是要在实例之间共享的吗?

标签: c++ list pointers


【解决方案1】:

这里没有性能原因。它只是所有权共享的一个案例。作为经验法则记住这一点:C++ 中的指针用于共享/传递资源的所有权,或通过动态绑定提供多态行为。

人们谈论性能是因为你避免复制这些东西。等等等等等等。 如果你需要复制,你应该复制。它使用指针的唯一原因是作者在复制事物列表时不想复制事物,换句话说,他/她想在两个位置(列表):正如我之前所说的,所有权共享。

另一方面,请注意该类名为basemesh。所以这里指针的真正意义可能是使用多态顶点、边等(动态绑定)。

注意:如果性能是这里的重点,我很确定作者会使用紧凑且对齐的 non-cache-miss-prone std::vector 而不是 std::list。在这种情况下,使用指针最可能的原因是多态性,而不是性能。与指针、取消引用和遍历链表相关的任何事情的性能总是低于紧凑数据,例如 std::vector&lt;Vertex&gt; 就是这样。 同样,如果指针的使用不是为了多态性,是为了所有权相关的事情,而不是性能。

其他说明:复制是的,您正在复制。但请注意复制的内容和方式。除了非常罕见的实现之外,顶点是成对的浮点数/整数。 复制 64 位浮点数与复制 32/64 位指针没有任何好处
另请注意,除了您不那么幸运,您正在复制存储在同一缓存行或几乎在缓存中的内容。

现在关于优化的一个好规则是:尽量优化内存访问,而不是 CPU 循环。我推荐这个线程:What is "cache-friendly" code?,这是一个实际案例:Why are elementwise additions much faster in separate loops than in a combined loop?。最后,this thread 包含有关使用现代编译器进行优化的精彩说明。

【讨论】:

    【解决方案2】:

    我的猜测是,它要么是为一个非常不寻常的特定情况而设计的,但更有可能的是,它是由一个不知道堆分配或std::list 实际工作原理的程序员编写的,只是盲目地使用指针。

    似乎非常不太可能std::list 指向单个顶点的指针是性能或设计方面的最佳选择。

    【讨论】:

      【解决方案3】:

      在实践层面上,如果一个方法改变了一个点,它不需要在其他数据结构中重现改变。他们都会指向同一个东西。

      但是在内存管理方面,使用智能指针是明智的,

      【讨论】:

        【解决方案4】:

        我猜我会说这是为了让这些对象可以有指向彼此的指针(例如,一个 Edge 可以有指向两个顶点的指针,每个顶点都可以有一个指向 Edge 的指针)。

        如果所有顶点都存在于 basemesh 中的 std::list 中,那么指向它们的指针将不可靠,尽管 list::iterators 可能工作得很好。

        【讨论】:

          【解决方案5】:

          在检索内部数据时,通常使用指针效率较低,因为每次访问它时都必须取消引用该值。

          但同时它在传递数据时会更有效率,因为你只是传递指针。我猜选择的解决方案与多个对象通过组合共享数据这一事实有关。例如:多个Edge 实例可以引用同一个Vertex

          现在std::list 保证包含的值的地址是一致的,直到元素本身被删除,所以实际上做类似的事情

          Edge(const Vertex *v1, const Vertex *v2) { .. }
          
          std::list<Vertex>::iterator it = std::advance(vertices.begin(), 3);
          std::list<Vertex>::iterator it2 = std::advance(vertices.begin(), 5);
          new Edge(&(*it), &(*it2));
          

          因为地址不会失效所以没有必要使用指针来存储对象。实际上,通过使用此解决方案,您不需要关心单个对象的内存管理,因为您不需要 delete 它们或将它们包装到智能指针中。

          【讨论】:

            【解决方案6】:

            出于性能原因并减少出错的机会,它使用指针。

            想象一下不使用指针的替代方案。每次插入类 basemesh 都会导致创建对象的副本,并且每次访问对象时,如果您不小心,也会得到一个副本。

            例如,想象一下这样的语句:

            Edge e = m_edges[0];
            e.doSomethingThatModifiesState();
            

            在此示例中,没有指针,您将拥有对象的副本,您对其执行的任何操作都不会影响存储在 m_edges 中的实际边缘对象。

            有了指针,你就没有这个问题了:

            Edge* e = m_edges[0];
            e->doSomethingThatModifiesState();
            

            在这个例子中,没有复制对象,当你做某事时,你会得到预期的行为。

            【讨论】:

              【解决方案7】:

              正如许多其他人所说,速度是最明显的原因。另一个原因是通过指向基类的指针来获得多态行为。

              【讨论】:

              • 谢谢。你能扩展多态行为点吗?
              猜你喜欢
              • 2011-04-26
              • 1970-01-01
              • 2011-08-29
              • 2011-06-04
              • 1970-01-01
              • 2016-03-03
              • 2013-06-13
              • 1970-01-01
              • 2014-05-07
              相关资源
              最近更新 更多