0

我制作了一个可爱的通用(即模板)List类来处理 C++ 中的列表。原因是我发现这个std::list类对于日常使用来说非常难看,而且由于我经常使用列表,我需要一个新的。主要的改进是在我的课堂上,我可以用来[]从中获取物品。此外,还有待实施的是IComparer对事物进行分类的系统。

我正在使用这个ListOBJLoader,我的类加载 Wavefront .obj 文件并将它们转换为网格。OBJLoader包含指向以下“类型”的指针列表:3D 位置、3D 法线、uv 纹理坐标、顶点、面和网格。顶点列表中的对象必须链接到所有 3D 位置、3D 法线和 uv 纹理坐标列表中的某些对象。面链接到顶点,网格链接到面。所以它们都是相互关联的。

为了简单起见,让我们考虑一下,在某些情况下,只有两个指针列表:List<Person*>List<Place*>. Person类包含,除其他外,字段List<Place*> placesVisitedPlace类包含字段List<Person*> peopleThatVisited。所以我们有这样的结构:

class Person
{
    ...
  public:
    Place* placeVisited;
    ...
};

class Place
{
    ...
  public:
    List<People*> peopleThatVisited;
};

现在我们有以下代码:

Person* psn1 = new Person();
Person* psn2 = new Person();

Place* plc1 = new Place();
Place* plc2 = new Place();
Place* plc2 = new Place();


// make some links between them here:
psn1->placesVisited.Add(plc1, plc2);
psn2->placesVisited.Add(plc2, plc3);

// add the links to the places as well
plc1->peopleThatVisited.Add(psn1);
plc2->peopleThatVisited.Add(psn1, psn2);
plc3->peopleThatVisited.Add(plc3);

// to make things worse:

List<Person*> allThePeopleAvailable;

allThePeopleAvailable.Add(psn1);
allThePeopleAvailable.Add(psn2);

List<Place*> allThePlacesAvailable;

allThePlacesAvailable.Add(plc1);
allThePlacesAvailable.Add(plc2);
allThePlacesAvailable.Add(plc3);

全部做完。当我们到达时会发生什么}?所有的 dtor 都被调用并且程序崩溃,因为它试图删除东西两次或更多次。

我的列表中的 dtor 如下所示:

~List(void)
{
    cursor = begin;
    cursorPos = 0;

    while(cursorPos &#60; capacity - 1)
    {
        cursor = cursor->next;
        cursorPos++;
        delete cursor->prev;
    }

    delete cursor;
}

哪里Elem是:

struct Elem
{
  public:
    Elem* prev;
    T value;
    Elem* next;
};

并且T是泛型List类型。

这让我们回到了这个问题:有什么方法可以安全地删除我的List课程?里面的元素可能是也可能不是指针,如果它们是指针,我希望能够在删除 myList时指定是要删除里面的元素还是只删除Elem它们周围的包装器。

智能指针可能是一个答案,但这意味着我不能有List<bubuType*>,而只有List<smart_pointer_to_bubuType>. 这可能没问题,但同样:声明 aList<bubuType*>不会导致错误或警告,并且在某些情况下智能指针会导致实现中的一些问题:例如,我可能想List<PSTR>为某些 WinAPI 返回声明 a。我认为让PSTR智能指针内部的那些将是一项丑陋的工作。因此,我认为我正在寻找的解决方案应该与List模板的释放系统有关。

有任何想法吗?

4

9 回答 9

11

甚至不看你的代码,我就说:废弃它!

C++ 有一个列表类模板,它几乎与它一样高效,为所有 C++ 程序员所熟知,并且您的编译器没有错误。

学习使用 STL。1来自其他 OO 语言的 STL 可能看起来很奇怪,但它的奇怪有一个潜在的原因,一种结合抽象和性能的外星美- 在 Stepanov 出现并想到 STL 之前,这被认为是不可能的。
请放心,您不是唯一一个为理解 STL 而苦苦挣扎的人。当它出现在我们身上时,我们都在努力掌握它的概念,学习它的特殊性,理解它是如何运作的。STL一头奇怪的野兽,但后来它成功地结合了每个人都认为永远无法结合的两个目标,所以一开始它看起来很陌生。

我敢打赌,编写自己的链表类曾经是 C++ 程序员第二受欢迎的室内运动——就在编写自己的字符串类之后。我们这些在 15 年前就已经在 C++ 编程的人现在喜欢撕掉那些在旧代码中腐烂的漏洞百出、效率低下、奇怪和未知的字符串、列表和字典类,并用非常有效的东西来代替它,嗯-已知,并且没有错误。开始你自己的列表类(除了教育目的)必须是最糟糕的异端之一。

如果您使用 C++ 进行编程,请尽快适应其中最强大的工具之一。

1请注意,术语“STL”命名了源自 Stepanov 库的 C++ 标准库的一部分(加上std::string事后附加的 STL 接口之类的东西),而不是整个标准库。

于 2011-03-10T18:42:38.850 回答
3

最好的答案是您必须考虑每个对象的生命周期,以及管理该生命周期的责任。

特别是,在人和他们访问过的地点的关系中,很可能他们中的任何一个都不应该自然而然地对其他人的一生负责:人们可以独立于他们访问过的地点生活,地方存在,无论是否他们被访问过。这似乎暗示人和网站的生命周期都与其他人无关,并且持有的指针与资源管理无关,而是引用(不是 C++ 意义上的)。

一旦您知道谁负责管理资源,那应该是应该的代码delete(或者如果需要动态分配,最好将资源保存在容器中或合适的智能指针中),并且您必须确保不会发生删除在引用相同元素的其他对象完成之前。

如果在一天结束时,您的设计所有权不清楚,您可以回退到使用shared_ptr(或booststd)小心不要创建会产生内存泄漏的循环依赖关系。再次shared_ptr正确使用 s 你必须回过头来思考,想想对象的生命周期......

于 2011-03-10T18:42:41.683 回答
3

如果您负责释放该内存,请始终使用智能指针。除非您知道您不负责删除该内存,否则永远不要使用原始指针。对于 WinAPI 返回,将它们包装到智能指针中。当然,原始指针列表不是错误,因为您可能希望拥有一个不属于您的内存的对象列表。但是避免使用智能指针肯定不能解决任何问题,因为它们是一个完全必要的工具。

只需使用标准列表。这就是它的用途。

于 2011-03-10T18:50:47.943 回答
1

在行中: // 在它们之间建立一些链接: psn1->placesVisited.Add(plc1, plc2); psn2->placesVisited.Add(plc2, plc3);

// 也添加到地点的链接 plc1->peopleThatVisited.Add(psn1); plc2->peopleThatVisited.Add(psn1, psn2); plc3->peopleThatVisited.Add(plc3);


您在堆上有包含指向彼此的指针的实例。不止一个,而且给多个人添加同一个Place指针也会导致问题(多次删除内存中的同一个对象)。

告诉您学习 STL 或使用 shared_ptr (Boost) 可能是一个很好的建议,但是,正如 David Rodríguez 所说,您需要考虑对象的生命周期。换句话说,您需要重新设计此场景,以使对象不包含彼此的指针。

示例:真的有必要使用指针吗?- 在这种情况下,如果您需要 STL 列表或向量,则需要使用 shared_ptr,但同样,如果对象相互引用,即使是最好的 shared_ptr 实现也无法实现。

如果需要地点和人员之间的这种关系,请设计一个类或使用一个容器来承载彼此的引用,而不是让人员和地点相互指向。就像 RDBMS 中的多对多表一样。然后,您将拥有一个类/容器,它将负责在进程结束时删除指针。这样,Places 和 Persons 之间的关系将不存在,仅存在于容器中。

问候,J.里韦罗

于 2011-03-10T19:07:24.137 回答
0

如果不查看 Add 函数的确切代码以及列表的析构函数,就很难确定问题所在。

但是,正如评论中所说,此代码的主要问题是您不使用std::listor std::vector。有经过验证的有效实现,可以满足您的需求。

于 2011-03-10T18:41:42.627 回答
0

首先,我当然会使用 STL(标准模板库),但是,我看到你正在学习 C++,所以作为练习,编写一个像 List 模板这样的东西会很好。

首先,您不应该公开数据成员(例如placeVisitedpeopleThatVisited)。这是面向对象编程的黄金法则。您应该为此使用 getter 和 setter 方法。

关于双重删除的问题:唯一的解决方案是在指针周围有一个包装类,以跟踪未完成的引用。看看boost::shared_ptr。(Boost是另一个精心设计的宏伟 C++ 库)。

于 2011-03-10T18:54:29.947 回答
0

程序崩溃是因为 delete 释放了指针的内存,因为它没有从列表中删除项目。您在至少两个列表中具有相同的指针,因此在同一块内存上多次调用 delete 会导致崩溃。

于 2011-03-10T18:55:21.797 回答
0

首先,智能指针不是这里的答案。他们要做的就是保证对象永远不会被删除(因为根据定义,双链表包含循环)。

其次,您无法将参数传递给析构函数,告诉它删除包含的指针:这必须通过列表的类型、其模板参数之一、部分特化或构造函数的参数来完成。(后者可能也需要部分特化,以避免尝试删除非指针。)

最后,不删除对象两次的方法是不对其调用两次 delete。我不太确定你的析构函数中发生了什么,但你从不改变光标,所以每次通过时,你都会删除相同的两个元素。你可能需要更多的东西:

while ( cursor not at end ) {
    Elem* next = cursor->next;
    delete cursor;
    cursor = next;
}

——詹姆斯·坎泽

于 2011-03-10T19:15:13.607 回答
-1

您可以轻松地围绕普通列表实现 [] :

template <class Type> 
class mystdlist : public std::list<Type> { 
public: 
    Type& operator[](int index) { 
       list<Type>::iterator iter = this.begin(); 

       for ( int i = 0; i < index; i++ ) { 
          iter++; 
       }
       return *iter; 
    }  
};  

恕我直言,您为什么要这样做很奇怪。如果您想要 O(1) 访问,请使用向量。

于 2011-03-10T19:01:42.763 回答