我制作了一个可爱的通用(即模板)List
类来处理C ++中的列表。原因是我发现std::list
类在日常使用中非常难看,而且由于我经常使用列表,我需要一个新的。主要的改进是,在我的班级,我可以使用[]
从中获取项目。此外,还有待实现的是IComparer
系统来对事物进行排序。
我在List
中使用这个OBJLoader
类,我的类加载Wavefront .obj文件并将它们转换为网格。 OBJLoader
包含指向以下“类型”的指针列表:3D位置,3D法线,uv纹理坐标,顶点,面和网格。顶点列表具有必须链接到所有3D位置,3D法线和uv纹理坐标列表中的某些对象的对象。面链接到顶点,网格链接到面。所以他们都是相互联系的。
为简单起见,让我们考虑一下,在某些情况下,只有两个指针列表:List<Person*>
和List<Place*>
。 Person
类除其他外包含字段List<Place*> placesVisited
,Place
类包含字段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 < 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
模板的释放系统相关。
有什么想法吗?
答案 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_ptr
(boost
或std
)注意不要创建循环会产生内存泄漏的依赖项。再次正确使用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::list
或std::vector
。经过验证的高效实施符合您的需求。
答案 5 :(得分:0)
首先,我肯定会使用STL(标准模板库),但是,我看到你正在学习C ++,所以作为一个练习,将这样的东西写成List模板会很好。
首先,您不应该公开数据成员(例如placeVisited
和peopleThatVisited
)。这是面向对象编程的黄金法则。你应该使用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)访问,请使用向量。