3

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

根据定义,网格数据结构应至少包含有关点坐标、边缘连接性和面部信息的信息。

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

然而网格类看起来像这样。

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<Vertex>

我已经看到在其他几个 C++ 代码中使用了这个结构

这与类的继承有关吗?或者它是否与迭代列表的性能有关?

顾名思义,这个 basemesh 类是派生其他专用网格的基类。

4

7 回答 7

2

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

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

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

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

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

现在关于优化的一个好规则是:尝试优化内存访问,而不是 CPU 循环。我推荐这个线程:什么是“缓存友好”代码?,这是一个实际案例:为什么在单独的循环中元素加法比在组合循环中快得多?. 最后,该线程包含有关使用现代编译器进行优化的好注释。

于 2013-11-05T21:17:10.500 回答
1

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

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

于 2013-11-05T21:55:25.723 回答
0

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

但就内存管理而言,明智的做法是使用智能指针,

于 2013-11-05T20:38:43.123 回答
0

猜测一下,我会说这些对象可以有指向彼此的指针(例如,边缘可以有指向两个顶点的指针,每个顶点都可以有一个指向边缘的指针)。

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

于 2013-11-05T20:39:04.937 回答
0

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

但与此同时,它在传递数据时会更有效率,因为你只是在传递指针。我猜选择的解决方案与多个对象通过组合共享数据这一事实有关。例如:多个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它们或将它们包装到智能指针中。

于 2013-11-05T20:40:40.023 回答
-1

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

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

例如,想象一下这个语句:

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

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

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

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

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

于 2013-11-05T20:40:02.533 回答
-1

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

于 2013-11-05T20:59:46.073 回答