可怕的代码

smartduck 2003-08-16 12:10:52
以下是段可怕的代码,STL为什么不禁止如此写法或改正此问题呢?(据我所知,大多的版本包括sgi stl都存在这样的问题)

...
{
list<int> IntList;
IntList.push_back(1);
IntList.push_back(2);
IntList.push_back(3);
IntList.push_back(4);
IntList.erase(IntList.end());
} //raise exception here
...全文
54 26 打赏 收藏 转发到动态 举报
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
smartduck 2003-08-20
  • 打赏
  • 举报
回复
谢谢.
widewave 2003-08-19
  • 打赏
  • 举报
回复
mark
smartduck 2003-08-19
  • 打赏
  • 举报
回复
谢谢楼上的。我也是觉得和你说的差不多。不过我觉得他解释的比较详细。我想知道他的全部意思。拜托你了。能帮我译下吗?
oopig 2003-08-19
  • 打赏
  • 举报
回复
以下是我的译文,凑合着看吧:
任何一个都不会绝对比另一个好。第一个实现直截了当;它更简单,而且简单就是一种美。第二个实现捕捉了内存越界的错误用法,但是增加了正常使用情况下的执行时间,而且:(a)这种实现诱导使用者依赖于未定义的行为,(b)不鼓励调用者按照和函数之间的契约来使用函数。(a)和(b)有许多交迭的地方,但是结果就是依赖于第二章实现(b)行为的C++代码是不容易移植的。
更好的办法是在debug模式下进行边界检查,断言'position'是个废弃的迭代器,这样子调用者的错误就被够被标记并纠正。我不知道STLPort的debug模式是不是这么做的,但是如果它采用类似的办法我不会觉得奇怪。
实现一个能够在任何错误使用情况下都工作的函数是不可能的。但是以性能的一些损耗作为代价来设计一个能够比C++捕捉到更多错误的编程环境是可能的,但是这些编程环境并不能察觉到大部分的逻辑错误。
最好的办法是依照一个简洁的契约并实现它。如果“那个”契约的实现不太损耗性能的话,那就是一个好的实现。但是容错性太好的函数反而把错误给隐藏了,以至于看上去是正常有效的,这样的做法对建立坚固的代码没有什么帮助。
当你发现一个BUG从而知道运行的程序处于一个非法状态的时候,就需要讨论什么是要做的正确事情(大意)。在大多数情况下,退出(可能有保存内存映像的选项)是最好和最安全的处理办法;在很少见的情况下,继续运行会更好,但是我没有遇到到这种情况。
smartduck 2003-08-19
  • 打赏
  • 举报
回复
这是我在comp.lang.c++.moderated上的提问。
还有其它人的回答:
tonci.tomic
instead of B) you can write:
template <class T>
T::iterator checked_erase(T& container, T::iterator position) {
if(position == container.end())
return container.end();
return container.erase(position);
}

so A is better because it is smaller, faster and standard.


llewelly
A good debugging implementation would assert if you passed it a
non-dereferenceable iterator. But since niether of these do that,
I don't see a convincing argument to prefer either.

Ulrich Eckhardt
The first. Giving an iterator to a non-existant object is a
programming-error. This includes passing end() just as well as passing
iterators to other containers or default-constructed ones. The check in
the second version only catches a small subset of them. Also, ignoring
programming-errors is not good. It should rather assert.

If you want to make it right, do it as STLport does it. Supply a special
debug-version where every iterator knows its container. That way, you can
find all faulty iterators and hasdle them accordingly.

Let me confess that things like
list.erase( find( list.begin(), list.end(), foo));
are tempting. However, I would not want to pay the extra check in cases
where I dont need it and there are things like remove() that can do the
job.

Michiel Salters
Better for what purpose? For an STL implementation, the first is more
efficient. The second turns a bug in user code into silent behavior.
I'd expect (in debug mode) that if this bug is detected, it is
signalled not hidden.

In a user library, for systems that should continue even in the
presence of bugs, I'd expect at least a call to an error logger.

However, a user library may of course be designed with this behavior
intended - but then we'd need to see the entire design to judge that.

wkoji 2003-08-19
  • 打赏
  • 举报
回复
good,
MARK
oopig 2003-08-19
  • 打赏
  • 举报
回复
翻译中。。。
有点好奇,楼主从哪里搞到这东东的,作者是谁?
紫郢剑侠 2003-08-19
  • 打赏
  • 举报
回复
恭喜楼主.谢谢.
oopig 2003-08-19
  • 打赏
  • 举报
回复
补充一下,关于“契约”的设计思想,你可以参考meyer的《面向对象构造》一书,经典之作。不过我没有看几页:P
oopig 2003-08-19
  • 打赏
  • 举报
回复
基本上就是我说的意思:按照契约(contract)办事,各司其职,保证参数的正确性是调用者的责任,而函数的责任就是根据假定参数的正确并以此为基础进行计算。
这个老外的态度还是很鲜明的,看看这段话你就明白了:
The best approach is to have a clean contract and to implement
it. If enforcement of *that* contract can be done without hurting
performance too much, that is a good thing, but to silently allow
*some* erroneous uses as if they were valid doesn't do much to
help to make for solid code.
其中他特别说到,容错性太好的函数反而把错误给隐藏了(silently allow),以至于看上去是正常有效的,这样的做法对建立坚固的代码(solid code)没有什么帮助。
bm1408 2003-08-19
  • 打赏
  • 举报
回复
good!
smartduck 2003-08-18
  • 打赏
  • 举报
回复
别人教我了,但是看不懂。如何翻译?谢谢!
> I just want to say: look at the following code, which is better, and why?
>
> A)
> iterator erase(iterator position) {
> link_type next_node = link_type(position.node->next);
> link_type prev_node = link_type(position.node->prev);
> prev_node->next = next_node;
> next_node->prev = prev_node;
> destroy_node(position.node);
> return iterator(next_node);
> }
>
> B)
> iterator erase(iterator position) {
>
> if (position == end())
> return end();
>
> link_type next_node = link_type(position.node->next);
> link_type prev_node = link_type(position.node->prev);
> prev_node->next = next_node;
> next_node->prev = prev_node;
> destroy_node(position.node);
> return iterator(next_node);
> }

Neither is completely better than the other. The first has
the advantage of being a more direct implementation of the
required semantics; it is simpler, and simplicity is a virtue.
The second catches one erroneous use out of many, adds execution
time to all proper uses, and (a) can tempt users to rely on the
undefined behavior as well as (b) discouraging users from using
functions as defined by their contracts. There's a lot of overlap
between (a) and (b), but the upshot is that code that relies on
the behavior of the second implementation is not portable C++.

Better would be something from a range-checking implementation
with a debug mode that asserted that 'position' is a dereferencable
iterator into the relevant container, so that the caller's error
can be flagged and corrected. I don't know offhand if STLPort's
debugging mode does this, but I wouldn't be surprised if it does
something similar.

It's *not* possible to implement a function so that it "works"
all the time when the caller doesn't use it correctly. It is
possible, at some cost in performance, to design programming
environments that can catch more errors than can C++, but they
can't detect most logic errors.

The best approach is to have a clean contract and to implement
it. If enforcement of *that* contract can be done without hurting
performance too much, that is a good thing, but to silently allow
*some* erroneous uses as if they were valid doesn't do much to
help to make for solid code.

This soon leads into discussions of what is The One True Thing To
Do when you know that the currently running program is in an
invalid state because of a bug (which is usually about all you
do know if an assertion fails). For many situations, exiting
(maybe with the option to dump data to a safe area) is the best
and safest thing to do here; on rare occasions it might be better
to stumble on, but I don't think I've come across such a situation.
solotony 2003-08-18
  • 打赏
  • 举报
回复
STL为什么不禁止如此写法或改正此问题呢?
因为它并无意义,end()是超过最后一个有效值的位置,删除一个位置有何意义呢?
删除end()是一个错误.
smartduck 2003-08-17
  • 打赏
  • 举报
回复
>对于这种问题应该加assert,然后提供给用户两个版本的库(测试和发行版)
>这样不会影响效率,也不影响调试

那为什么没有加呢?
echoher 2003-08-16
  • 打赏
  • 举报
回复
对于这种问题应该加assert,然后提供给用户两个版本的库(测试和发行版)
这样不会影响效率,也不影响调试

用户使用一个函数时不能假设这个函数能判断参数是否合法,他要自己判断
如果函数里写了判断代码,就会重复判断

有个办法是写个包装函数,对参数进行检验
made_in_ 2003-08-16
  • 打赏
  • 举报
回复
确实,这个写法并不能说明stl的问题,而是应该由程序员来考虑的.因为这种写法并无语法上的错误.至于逻辑和语意错误,我们也不能要求stl自动处理啊.
happycock 2003-08-16
  • 打赏
  • 举报
回复
从来不喜欢写I/O,不喜欢写通用程序,用户太可怕了……
zhoutian618 2003-08-16
  • 打赏
  • 举报
回复
同意,
可怕的是的人!!!
yjh1982 2003-08-16
  • 打赏
  • 举报
回复
可怕的是人,不是代码
smartduck 2003-08-16
  • 打赏
  • 举报
回复
其实我的意思是说,向borland C++ complier中的做法是不是比其他的做法会好写呢?
to oopig(面向对象的猪):
1)加上下面这段代码对性能应该影响不会太大吧。
if (position == end())
return end();
2)如果说“保证参数的正确性是调用者的责任”,那系统中是不是可以把大多数的向1)这样的判断都去掉呢?如否,那什么时候该判断呢?什么时候不用判断呢?如果从契约式的编程思想解释这个问题,那也该加上assert嘛。这对性能应该也不会影响太大吧。

这些都太简单了。我们都可以理解的了,专家肯定也知道!我觉得我们还是没有找到真正的理由。
加载更多回复(6)

24,854

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 工具平台和程序库
社区管理员
  • 工具平台和程序库社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧