我记得我听说下面的代码不符合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;
}
答案 0 :(得分:8)
您的代码有效,但更好的解决方案是使用智能指针。
问题是std::vector
的所有要求都位于C ++标准的23.2.4部分。无效指针没有限制。 std::vector
与int*
一样适用于任何其他类型(我们不考虑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
根据标准,简单地创建一个野指针是合法的。可能不是一个好主意,但仍然是合法的。
标准中有关指针类型的更多信息。
所以说标准(3.9.2.3):
...对象指针的有效值 type表示地址 内存中的一个字节(1.7)或null 指针(4.10)......
......关于“内存中的一个字节”,(1.7.1):
C中的基本存储单元 + +内存模型是字节。一个字节至少足以容纳 基本执行的任何成员 字符集,由一个 连续的比特序列, 数量是 实现定义。至少 重要的一点叫做 低阶位;最重要的 bit称为高位。该 内存可用于C ++程序 由一个或多个序列组成 连续的字节。每个字节都有一个 独特的地址。
这里没有任何关于该字节是生活Foo
的一部分,关于你有权访问它,或任何类型的东西。它只是内存中的一个字节。