从C ++中的指针向量中删除元素

时间:2010-06-16 18:37:26

标签: c++ vector stl delete-operator

我记得我听说下面的代码不符合C ++,并且希望拥有比我更多C ++法律术语的人能够确认或否认它。

std::vector<int*> intList;
intList.push_back(new int(2));
intList.push_back(new int(10));
intList.push_back(new int(17));

for(std::vector<int*>::iterator i = intList.begin(); i != intList.end(); ++i) {
  delete *i;
}
intList.clear()

基本原理是向量包含指向无效内存的指针是非法的。现在显然我的例子将编译,它甚至可以在我知道的所有编译器上工作,但是它是否符合标准的C ++,或者我应该执行以下操作,我被告知其实是符合标准的方法:

while(!intList.empty()) {
  int* element = intList.back();
  intList.pop_back();
  delete element;
}

8 个答案:

答案 0 :(得分:8)

您的代码有效,但更好的解决方案是使用智能指针。

问题是std::vector的所有要求都位于C ++标准的23.2.4部分。无效指针没有限制。 std::vectorint*一样适用于任何其他类型(我们不考虑vector<bool>的情况),它并不关心它们指向的位置。

答案 1 :(得分:5)

你的代码很好。如果您担心某些元素暂时无效,请将循环体改为

int* tmp = 0;
swap (tmp, *i);
delete tmp;

答案 2 :(得分:2)

C ++哲学是让程序员尽可能多地拥有自由,并且只禁止实际会造成伤害的事情。无效的指针本身没有任何危害,因此您可以自由地使用它们。什么会造成伤害是以任何方式使用指针,因此会调用未定义的行为。

答案 3 :(得分:1)

归根结底,这是个人品味的问题。有一个包含无效指针的向量不是“标准不兼容”,但它是危险的,就像任何指向无效内存的指针一样危险。你的后一个例子将确保你的向量永远不会包含错误的指针,是的,所以这是最安全的选择。

但是如果你知道在你前一个例子的循环中永远不会使用该向量(例如,如果向量是局部作用域的话),那就完全没问题。

答案 4 :(得分:0)

你在哪里听到的?考虑一下:

std::vector<int *> intList(5);

我刚创建了一个填充了5个无效指针的向量。

答案 5 :(得分:0)

在容器中存储原始指针(我不推荐这个)然后必须进行2阶段删除,我会在第二个选择第一个选项。

我相信container :: clear()会比一次弹出一个项目更有效地删除地图内容。

你可能可以将for循环转换为一个很好的(伪造的)forall(begin(),end(),delete)并使它更通用,所以如果你从vector更改为其他容器,它甚至不重要。

答案 6 :(得分:0)

我认为这不符合标准。 C ++标准定义了语言和实现要求的语法。您正在使用STL这是一个功能强大的库,但是像所有库一样,它不是C ++本身的一部分......虽然我想可以说,当积极使用时,像STL和Qt这样的库将语言扩展为不同的超集语言

无效指针完全符合C ++标准,当您取消引用它们时,计算机就不会喜欢它。

您所询问的更多是最佳实践问题。如果您的代码是多线程的并且可能共享intList,那么您的第一种方法可能会更危险,但正如Greg所建议的那样,如果您知道intList无法访问,那么第一种方法可能是更高效。也就是说,我认为在你知道存在性能问题之前,安全通常应该在权衡中获胜。

根据契约式设计概念的建议,所有代码都定义了隐式或显式的契约。像这样的代码的真正问题是你向用户承诺:前置条件,后置条件,不变量等。库创建了一个特定的契约,你编写的每个函数都定义了自己的契约。你只需要为你的代码选择适当的余额,只要你明确告诉用户(或者你自己六个月之后)什么是安全的,什么是不安全的,就可以了。

如果有与API一起记录的最佳实践,请尽可能使用它们。出于某种原因,它们可能是最佳实践。但请记住,最好的做法可能是在旁观者的眼中......这可能不是所有情况下的最佳做法。

答案 7 :(得分:0)

  

向量包含是非法的   指向无效内存的指针

这就是标准对容器内容的评价:

(23.3):存储在这些组件中的对象类型必须满足 CopyConstructible 类型(20.1.3)的要求,以及可分配类型的附加要求。

(20.1.3.1,CopyConstructible):在下面的表30中,T是由实例化模板的C + +程序提供的类型,t是类型T的值,u是类型const的值吨。

expression  return type  requirement
xxxxxxxxxx    xxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
T(t)                       t is equivelant to T(t)
T(u)                       u is equivelant to T(u)
t.~T()      
&t          T*           denotes the address of t
&u          const T*     denotes the address of u

(23.1.4,Assignable):64,T是用于实例化容器的类型,t是T的值,u是值(可能 const)T。

expression  return type  requirement
xxxxxxxxxx    xxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
t = u         T&           t is equivilant to u

关于STL集合的内容就是这么说的。它没有说明指针,它对指向有效内存的指针特别保持沉默。

因此,delete中的vector指针,虽然很可能是一个非常糟糕的架构决策,并且在星期六晚上3点凌晨使用调试器发出痛苦和痛苦的邀请,但却是完美的合法的。

编辑:

关于Kranar的评论,“分配指向无效指针值的指针会导致未定义的行为”。不,这是不正确的。此代码完全有效:

Foo* foo = new Foo();
delete foo;
Foo* foo_2 = foo;  // This is legal

什么是非法的是试图用那个指针做某事(或foo,就此而言):

delete foo_2; // UB
foo_2->do_something(); // UB
Foo& foo_ref = *foo_2; // UB

根据标准,简单地创建一个野指针是合法的。可能不是一个好主意,但仍然是合法的。

EDIT2:

标准中有关指针类型的更多信息。

所以说标准(3.9.2.3):

  

...对象指针的有效值   type表示地址   内存中的一个字节(1.7)或null   指针(4.10)......

......关于“内存中的一个字节”,(1.7.1):

  

C中的基本存储单元   + +内存模型是字节。一个字节至少足以容纳   基本执行的任何成员   字符集,由一个   连续的比特序列,   数量是   实现定义。至少   重要的一点叫做   低阶位;最重要的   bit称为高位。该   内存可用于C ++程序   由一个或多个序列组成   连续的字节。每个字节都有一个   独特的地址。

这里没有任何关于该字节是生活Foo的一部分,关于你有权访问它,或任何类型的东西。它只是内存中的一个字节。