无数次我在访问其内存之外的std::vector
或std::string
后编写了生成分段错误的代码:
std::string test{"hello!"};
std::cout << test[12] << std::endl;
这是一个错误,可以在运行时在非优化/调试构建中捕获,只需要一个简单断言的额外成本。 (但由于我们在没有-DNDEBUG
且没有-O3
的情况下建设,我们并不期望获得最高性能。)
为什么std::string::operator[]
没有这样实现?
标准是否禁止在库代码中使用断言?
char std::string::operator[](std::size_t i)
{
// `assert_with_message` only exists in debug mode
#ifndef NDEBUG
assert_with_message(i < this->size(),
"Tried to access character " + std::to_string(i)
+ " from string '" + *this + "' of size "
+ std::to_string(this->size()));
#endif
return data[i];
}
在没有-DNDEBUG
的情况下编译程序会非常有用,并在运行时看到与此消息类似的内容:
断言:试图从字符串中访问字符12&#39;你好!&#39;的 6号。
按(0)继续。
按(1)中止。
请注意,术语断言我指的是应该从发布/优化版本中完全删除/优化的开发/调试版本检查。
答案 0 :(得分:4)
标准库的几个实现确实在调试模式下提供了这样的检查,但调试模式不受NDEBUG控制。对于libstdc ++,您需要-D_GLIBCXX_DEBUG
(请参阅doc)。
答案 1 :(得分:3)
你做的是未定义的行为。由于此时允许任何操作,因此触发断言也可以。这是一个实施质量的东西,看起来libstdc ++在这里不是很好。
答案 2 :(得分:1)
有不同的标准库实现。他们中的一些人(msvc 10 for one),其中一些不是(gcc)。 不这样做的原因是它可能会在调试版本中大大减慢速度,使其不再可用。通常这样的实现仍然提供一些define标志,因此您可以打开它(-D_GLIBCXX_DEBUG用于gcc)。另一方面,如果需要,msvc提供_ITERATOR_DEBUG_LEVEL宏来关闭它。
答案 3 :(得分:-1)
我认为在标准版本中无法完成断言的原因非常明显:它们需要付出代价,并且尝试访问不存在的索引中的某些内容是一个错误你的代码,而不是标准库的代码。
这可能是C和C ++以及大多数高级语言的不同之处:对于正确的调用而言,期望的行为通常更多是一致而不是容错。
有很多理由不为许多情况抛出异常,而是返回一些指示操作成功的东西(例如,假设你想使用字符串对象的find方法 - 出于性能原因,以及也因为&#34;没有找到&#34;听起来不太可能。)
最重要的是,必须意识到抛出一个异常是一个非常复杂的过程,在运行时明智:你初始化相应异常的新对象,然后开始冒泡调用层次结构。通常情况下,在灾难性故障的情况下(例如程序员没有检查索引是否在范围内),这甚至可能使整个事情变得更糟(例如,尽管你可能认为C ++只运行在&#34;正确的&#34; PC上内存负载,还有微控制器执行用c ++编写的程序,一个例外可能只占用所有内存。)
总而言之,C ++只是没有你的背影。这不是你的父亲,教你如何骑自行车,如果你的程序开始甩动,轻轻地让你保持直立。相当于一些人允许你租一辆法拉利:他不会教你如何在转弯时看着你的肩膀,但他在做的时候并没有评论你的驾驶风格高速公路上也有250公里/小时的高速公路。你可以用法拉利做一些很棒的事情,但是如果你不自己照顾,你骑在墙上时会有很快的速度。
它并非设计为Java(通过强迫您捕获异常,或者刚刚发生或者是如此灾难性的事情,以及您可以放弃您的软件而不断妨碍您崩溃),也不能成为python(不是强迫你做任何事情,而是作为一种脚本语言,其中生成异常对象的努力相对正常/小解析)。
C ++希望您阅读文档,并谨慎使用或采用适当的方法。许多容器具有不同的访问方法,一些具有检查,一些不具有。在许多情况下,你只是在迭代那里的索引(例如你做for(int i = 0; i < container.length(); i++)
之类的事情),所以你不必每次都要检查(只会浪费在某些情况下,您需要自己进行检查,在某些情况下,您可以使用str.at(i)
,并且库会进行检查。