Why does operator ++ return a non-const value?
我看过Scott Meyers写的C++第三版。
本书的第3项"尽可能使用const",指出如果我们想防止将rvalues意外分配给函数的返回值,则返回类型应为const。
例如,iterator的增量函数:
1 2 3
| const iterator iterator::operator++(int) {
...
} |
这样就避免了一些事故的发生。
1 2 3 4 5 6 7
| iterator it;
// error in the following, same as primitive pointer
// I wanted to compare iterators
if (it++ = iterator()) {
...
} |
但是,gcc中的std::vector::iterator等迭代器不会返回const值。
1 2
| vector<int> v;
v.begin()++ = v.begin(); // pass compiler check |
有什么原因吗?
- 无论如何,您不应该在比较表达式的任何一边使用+。
- 令人惊讶的是,这得到了多少错误的答案。
- 这是一个过时的技巧,因为它可以防止移动结构。现代的方法是使用&引用限定符将operator=定义为默认值。
- 如果返回的迭代器是常量,那么您不能增加它们,因此迭代器不会进行迭代。
- @康拉德:写下你认为正确的东西,而不仅仅是翻阅。
- @Stefaanv I似乎不能够增加它们是正确的,因为我们不能增加后增量指针。
- @汤姆,我不是在捣乱。你之前已经有三个人承认他们误解了这个问题。我是建设性的,这就是为什么我改变了标题。我不回答这个问题,因为我不知道答案。有正当理由吗?
- @stefaanv:返回值仅用于初始化迭代所用的迭代器;无论返回值是否为const,这都有效。
- @迈克:在写这篇文章的时候,还不清楚op是否意味着post increment操作符的返回值应该是const。在他对我的评论作出回答之后,现在更清楚了。
- @康拉德:"令人惊讶的是,这得到了多少错误的答案"并不是一个建设性的评论。
- 我在这里用了双引号:"可能是因为这本书是在迈耶的书之前写的,现在修好它已经太迟了!"在野外可能有一些代码可以做你想阻止的事情…
- @尼姆:"可能有一些代码在野外,它可以做你想阻止的事情。"+1。如果可能的话,再加上向后兼容的需求,这可能是问题的答案。
- @康拉德非常感谢你的帮助:)
- 另请参见Herb Sutter的Gotw_6,const correction,其中包括指导原则(可论证的)。对于非内置返回类型,按值返回通常应为const。"它有警告"Lakos(第618页)反对返回const值,并注意到对于内置返回(例如,返回"const int")来说是多余的,他注意到可能会在模板实例化。"(我能想到的唯一"干扰"就是这个问题的答案)。
我很肯定这是因为它会破坏右值引用和任何类型的decltype。即使这些特征不在C++ 03中,它们也已经知道了。
更重要的是,我不认为任何标准函数返回常量值,它可能是直到标准发布之后才被考虑的。此外,常量值通常被认为是不正确的?.并非所有非常量成员函数的使用都是无效的,返回常量值会空白地阻止它们。
例如,
1
| auto it = ++vec.begin(); |
是完全有效的,而且确实是有效的语义,如果不完全需要的话。考虑一下我提供方法链的类。
1 2 3 4 5 6 7 8 9 10
| class ILikeMethodChains {
public:
int i;
ILikeMethodChains& SetSomeInt(int param) {
i = param;
return *this;
}
};
ILikeMethodChains func() { ... }
ILikeMethodChains var = func().SetSomeInt(1); |
是否应该仅仅因为有时我们可能调用一个没有意义的函数而不允许这样做?不,当然不是。或者"swaptimization"怎么样?
1 2 3
| std::string func() { return"Hello World!"; }
std::string s;
func().swap(s); |
如果func()生成了一个常量表达式,这将是非法的,但它是完全有效的,而且确实,假设std::string的实现没有在默认构造函数中分配任何内存,既快速又清晰/可读。
你应该认识到,C++ 03的价值/价值规则坦率地说是没有道理的。事实上,它们只是部分烘焙,在允许一些可能的权利的同时,最起码要禁止一些公然的错误。C++ 0x rValy规则更加简洁和完善。
- const rvalue不是重言式吗?我的意思是,没有地址的右值不是常量吗?
- @达夫卡:不,它们只是不能绑定到非常量引用。他们自己根本不是警察。
- +最后一段为1
- 写关于rvalues的文章并不能回答这个问题。这个问题询问了迭代器递增前和递增后运算符返回lvalue的原因。关于这个问题的规则并没有用C++ 11改变,所以我质疑这些规则更为完善和完善(至少关于手头的问题)。
- @大卫·哈曼:RValues和这个问题有着千丝万缕的联系,它问的是把const rvalues返回到非const rvalues。我的答案清楚地说明了为什么const r值是不可取的,以及C++ 11如何解决返回非conrrrand的问题,并保持所有的优点。
- 我认为像您的示例一样使用后递增迭代器是不正确的,但是您指出的原因是合理的和有趣的。
- @天山:这是正确的,如果不是成语。
如果it是非常量,我希望*(++it)给予我对它所代表的事物的可变访问。
然而,取消对const迭代器的引用只会产生对它所代表的事物的不可变访问。.[strike>[编辑:否,这也是错误的。我真的放弃了!]
这是我能想到的唯一原因。
正如您正确地指出的,以下是不正确的,因为基元上的++生成一个右值(左值上不能有右值):
所以这里的语言似乎有点不一致。
- 这也不是OP所要求的(顺便说一下,其他评论在哪里解释这一点?删除?).for循环甚至没有使用问题中提到的行为。
- @康拉德:是的。(是的,他们是。)我最后一段解释了OP认为const iterator会做什么和它实际会做什么之间的区别。以及它实际作用的原因(这是他要求的)。循环绝对使用行为:这个问题专门讨论了op++,这在const对象上是不可能的。很难与无法用于迭代的内容进行迭代。
- @汤姆,我认为你错了,但你可以通过解释你的回答中的意思来说服我,让我相信你的回答是对的。另外,请注意更改后的问题标题。这个问题与迭代器完全无关(这只是一个例子),它与operator ++的返回类型有关。你的评论也错了:it不是const。不过,++it的返回值是。没问题。
- @康拉德:在一场关于"行动"的争论中,你改变了标题。我建议你把它交给行动小组来澄清,以防你错了。也就是说,我同意你的观点。我已经重写了我的答案。
- @汤姆,我把标题改了,因为它误导了我。我读过梅耶斯书中的相关章节,所以我确信我知道他实际上在说什么(并且,通过推断,OP的意思是什么)。
- @康拉德:知道梅耶斯在说什么,和知道OP在说什么是完全正交的。
- 我要写一个新答案。这个会保留几分钟,让我们的对话自然结束。:)
- 即使operator++(int)返回const iterator,您编辑的示例也是有效的。
- @我认为我没有混淆const iterator和const_iterator。后递增的原始指针不能递增,例如,int* p; (p++)++;。但是gcc中的迭代器可以。
- 哎呀,我编辑了而不是作为新的发布。今天失败了。
- 已发布最终编辑。放弃了。
编辑:这并不能真正回答评论中指出的问题。我就把这封信留在这里,以防它有用…
我认为这在很大程度上是一个面向更好的可用接口的语法统一问题。当提供这样的成员函数而不区分名称,并且只让重载解析机制确定正确的版本时,您会阻止(或至少尝试)程序员使const相关的担忧。
我知道这似乎是矛盾的,特别是以你为例。但是,如果您考虑大多数用例,它是有意义的。采用类似于std::equal的STL算法。无论容器是否为常量,您总是可以编写类似于bool e = std::equal(c.begin(), c.end(), c2.begin())的代码,而无需考虑正确的开始和结束版本。
这是STL中的一般方法。记得operator[]…考虑到容器将与算法一起使用,这是合理的。尽管在某些情况下您可能仍然需要定义一个具有匹配版本(iterator或const_iterator的迭代器,这一点也很明显。
好吧,这正是我现在想到的。我不确定这有多令人信服…
注意:使用常量迭代器的正确方法是通过const_iteratortypedef。
- 这真的无关紧要。std::equal将按值取迭代器,完全否定特定迭代器值可能具有的任何常量。
- @死神-也许你错过了地点…?要点是,如果您在一个上下文中有一个对容器的const的引用,并且您需要调用std::equal,例如,您不必担心调用这样的c.beginconstversion()函数,您只需调用c.begin(),编译器就可以做到这一点。
- @itcmelo:除了返回一个const_iterator,而不是一个const iterator。问题及其背后的语义与访问容器无关——它们是关于访问函数返回的值。不管是引用还是常量/可变引用,这都是正确的。
- @戴德姆:是的,我明白了。你说得对。