在C ++中动态分配列表

时间:2011-03-10 18:28:24

标签: c++ list pointers dynamic-memory-allocation dangling-pointer

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

我在List中使用这个OBJLoader类,我的类加载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);

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

我列表的内容如下所示:

~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课程?里面的元素可能是也可能不是指针,如果它们是指针,我希望能够在删除List时指定是否要删除内部元素或只删除{{1}他们周围的包装。

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

有什么想法吗?

9 个答案:

答案 0 :(得分:11)

甚至没有查看你的代码,我说:废弃它!

C ++有一个列表类模板,大概是 高效 众所周知 给所有人C ++程序员,使用编译器来 无bug

学习使用STL。 1 来自其他OO语言,STL可能看起来很奇怪,但它有一个潜在的原因陌生,一个外星人的美丽 结合抽象和表现 - 在Stepanov来到STL之前被认为是不可能的事情。
请放心,您并不是唯一一个努力理解STL的人。当它出现在我们身上时,我们都在努力去理解它的概念,学习它的特性,理解它是如何发展的。 STL 是一个奇怪的野兽,但是它设法将每个人认为永远无法组合的两个目标结合在一起,所以一开始就让它变得陌生。

我打赌编写自己的链表类曾经是C ++程序员第二大最受欢迎的室内运动 - 就在编写自己的字符串类之后。我们这些15年前一直在编程C ++的人现在喜欢扯掉那些在旧代码中腐烂的错误,低效,奇怪和未知的字符串,列表和字典类,并将其替换为非常 < em>高效 众所周知 无错误 。开始自己的列表课程(除教育目的外)必须是最糟糕的异端之一。

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

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

答案 1 :(得分:3)

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

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

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

如果在一天结束时,在您的设计中所有权不明确,您可以回退使用shared_ptrbooststd)注意不要创建循环会产生内存泄漏的依赖项。再次正确使用shared_ptr s你必须回过头来思考,考虑对象的生命周期......

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

在线: //在这里建立一些链接: psn1-&gt; placesVisited.Add(plc1,plc2); psn2-&gt; placesVisited.Add(plc2,plc3);

//也可以添加指向这些地方的链接 plc1-&GT; peopleThatVisited.Add(PSN1); plc2-&gt; peopleThatVisited.Add(psn1,psn2); plc3-&GT; peopleThatVisited.Add(PLC3);


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

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

示例:是否真的有必要使用指针? - 在这种情况下,如果你需要STL列表或向量,你需要使用shared_ptr,但是如果对象相互引用,那么即使最好的shared_ptr实现也不会这样做。

如果需要地方和人之间的这种关系,设计一个类或使用一个容器,该容器将相互引用,而不是让人和地点相互指向。就像RDBMS中的多对多表一样。然后你将有一个类/容器,它将在过程结束时删除指针。这样,只有在容器中才会存在“地方”和“人”之间的关系。

问候,J。Rivero

答案 4 :(得分:0)

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

但是,正如评论中所述,此代码的主要问题是您不使用std::liststd::vector。经过验证的高效实施符合您的需求。

答案 5 :(得分:0)

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

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

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

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

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

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

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

- 詹姆斯坎泽

答案 8 :(得分:-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)访问,请使用向量。