1

我记得听说以下代码不符合 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;
}
4

8 回答 8

8

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

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

于 2010-06-16T18:42:28.900 回答
5

你的代码很好。如果您出于某种原因担心元素暂时无效,请将循环体更改为

int* tmp = 0;
swap (tmp, *i);
delete tmp;
于 2010-06-16T18:55:24.120 回答
2

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

于 2010-06-16T19:16:03.967 回答
1

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

但是,如果您知道在前一个示例的循环中永远不会使用该向量(例如,如果该向量是局部作用域的),那很好。

于 2010-06-16T18:42:39.003 回答
0

你是从哪里听来的?考虑一下:

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

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

于 2010-06-16T18:44:16.760 回答
0

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

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

您可能可以将 for 循环变成一个不错的(伪)循环forall(begin(),end(),delete)并使其更通用,因此即使您从向量更改为其他容器也没关系。

于 2010-06-16T19:07:17.650 回答
0

我认为这不是标准合规问题。C++ 标准定义了语言的语法和实现要求。您正在使用 STL,它是一个强大的库,但与所有库一样,它不是 C++ 本身的一部分……尽管我猜可能有人认为,当积极使用时,像 STL 和 Qt 这样的库将语言扩展为不同的超集语言.

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

您要问的更多是最佳实践问题。如果您的代码是多线程的并且intList可能共享,那么您的第一种方法可能更危险,但正如 Greg 建议的那样,如果您知道intList无法访问,那么第一种方法可能更有效。也就是说,我相信安全通常应该在权衡取舍中取胜,直到您知道存在性能问题。

正如契约式设计概念所建议的那样,所有代码都定义了一个契约,无论是隐式的还是显式的。像这样的代码的真正问题是您向用户承诺什么:前置条件、后置条件、不变量等。库制定了特定的合同,您编写的每个函数都定义了自己的合同。你只需要为你的代码选择适当的平衡点,只要你让用户(或六个月后的你自己)清楚什么是安全的,什么是不安全的,就可以了。

如果 API 记录了最佳实践,请尽可能使用它们。出于某种原因,它们可能是最佳实践。但请记住,最佳实践可能在旁观者的眼中......也就是说,它们可能不是所有情况下的最佳实践。

于 2010-06-16T19:31:36.443 回答
0

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

这就是标准对容器内容的规定:

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

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

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在 a 中插入指针vector,虽然很可能是一个非常糟糕的架构决策,并且在周六晚上 3:00 AM 调试器会带来痛苦和折磨,但它是完全合法的。

编辑:

关于 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

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

编辑2:

更多关于指针类型的标准。

标准(3.9.2.3)这么说:

...对象指针类型的有效值表示内存中字节的地址(1.7)或空指针(4.10)...

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

C++ 内存模型中的基本存储单元是字节。一个字节至少大到足以包含基本执行字符集的任何成员,并且由连续的位序列组成,其数量由实现定义。最低有效位称为低位;最高有效位称为高位。C++ 程序可用的内存由一个或多个连续字节序列组成。每个字节都有一个唯一的地址。

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

于 2010-06-16T19:44:16.450 回答