std :: string和string文字不一致

我在C ++ 0x中发现了std::string和string文字之间令人不安的不一致:

 #include <iostream> #include <string> int main() { int i = 0; for (auto e : "hello") ++i; std::cout << "Number of elements: " << i << '\n'; i = 0; for (auto e : std::string("hello")) ++i; std::cout << "Number of elements: " << i << '\n'; return 0; } 

输出是:

 Number of elements: 6 Number of elements: 5 

我理解为什么会发生这种情况的机制:string文字实际上是一个包含空字符的字符数组,当基于范围的for循环在字符数组上调用std::end()时,它会得到一个指针数组的末尾; 由于空字符是数组的一部分,所以它得到一个超过空字符的指针。

然而,我认为这是非常不可取的: std::string和string文字在属性的基本长度上应该是相同的?

有没有办法解决这种不一致? 例如,可以为字符数组重载std::begin()std::end() ,以便它们分隔的范围不包括终止空字符? 如果是这样,为什么不这样做?

编辑 :为了certificate我的愤慨更多的人说,我只是在使用C风格的string是一个“遗留function”的后果,请考虑如下代码:

 template <typename Range> void f(Range&& r) { for (auto e : r) { ... } } 

你会期望f("hello")f(std::string("hello"))做一些不同的事情吗?

如果我们重载了const char数组的std::begin()std::end()来返回一个小于数组的大小,那么下面的代码会输出4而不是预期的5:

 #include <iostream> int main() { const char s[5] = {'h', 'e', 'l', 'l', 'o'}; int i = 0; for (auto e : s) ++i; std::cout << "Number of elements: " << i << '\n'; } 

然而,我认为这是非常不可取的:std :: string和string文字在属性的基本长度上应该是相同的?

根据定义,string文字在string的末尾有一个(隐藏的)空字符。 Std :: strings没有。 由于std :: strings有一个长度,空字符有点多余。 string库的标准部分显式允许非空终止的string。

编辑
我认为我从来没有给出一个更有争议的答案,因为有大量的投票和大量的投票。

应用于C样式数组的auto迭代器在数组的每个元素上迭代。 范围的确定是在编译时进行的,而不是运行时间。 这是不合格的,例如:

 char * str; for (auto c : str) { do_something_with (c); } 

有些人使用chartypes的数组来保存任意数据。 是的,这是一种旧式的思维方式,也许他们应该使用C ++风格的std ::数组,但是这个构造是相当有效的,而且相当有用。 如果他们的自动迭代器超过char buffer[1024]; 仅仅因为该元素恰好与空字符具有相同的值而停在元素15处。 通过Type buffer[1024];的自动迭代器Type buffer[1024]; 将一路跑到最后。 是什么让一个字符数组如此值得一个完全不同的实现?

请注意,如果你希望在字符数组上的自动迭代器提前停止,有一个简单的机制来做到这一点:添加一个if (c == '0') break; 声明到你的循环体。

底线:这里没有不一致。 char []数组上的自动迭代器与自动迭代器如何处理其他C风格的数组一致。

在第一种情况下,你得到6是一个抽象的泄漏,无法避免在C. std::string “修复”。 为了兼容性,C风格string文字的行为在C ++中不会改变。

例如,可以为字符数组重载std :: begin()和std :: end(),以便它们分隔的范围不包括终止空字符? 如果是这样,为什么不这样做?

假设通过指针访问(而不是char[N] ),只能通过在包含字符数的string中embedded一个variables,以便不再需要查找NULL 。 哎呀! 这是std::string

“解决不一致”的方法根本不是使用遗留function

根据N3290 6.5.4,如果范围是一个数组,边界值将自动初始化,而不用begin / end函数调度。
那么,准备一下如下的包装呢?

 struct literal_t { char const *b, *e; literal_t( char const* b, char const* e ) : b( b ), e( e ) {} char const* begin() const { return b; } char const* end () const { return e; } }; template< int N > literal_t literal( char const (&a)[N] ) { return literal_t( a, a + N - 1 ); }; 

那么下面的代码将是有效的:

 for (auto e : literal("hello")) ... 

如果您的编译器提供用户定义的文字,可能有助于缩写:

 literal operator"" _l( char const* p, std::size_t l ) { return literal_t( p, p + l ); // l excludes '\0' } for (auto e : "hello"_l) ... 

编辑:下面将有较小的开销(用户定义的文字将不可用虽然)。

 template< size_t N > char const (&literal( char const (&x)[ N ] ))[ N - 1 ] { return (char const(&)[ N - 1 ]) x; } for (auto e : literal("hello")) ... 

如果你想要的长度,你应该使用strlen()的Cstring和.length()的C + +string。 你不能一视同仁Cstring和C ++string – 它们有不同的行为。

不一致性可以使用C ++ 0x的工具箱中的另一个工具来解决:用户定义的文字。 使用适当定义的用户定义的文字:

 std::string operator""s(const char* p, size_t n) { return string(p, n); } 

我们将能够写:

 int i = 0; for (auto e : "hello"s) ++i; std::cout << "Number of elements: " << i << '\n'; 

现在输出预期的数字:

 Number of elements: 5 

有了这些新的std :: string文字,可以说没有更多的理由使用C风格的string文字了。