我读过Scott Meyers编写的Effective C ++ 3rd Edition。
本书的第3项“尽可能使用const
”,如果我们想要防止rvalues被意外地分配给函数的返回值,则返回类型应为const
。
例如,iterator
的增量函数:
const iterator iterator::operator++(int) {
...
}
然后,一些事故被阻止了。
iterator it;
// error in the following, same as primitive pointer
// I wanted to compare iterators
if (it++ = iterator()) {
...
}
但是,GCC中的std::vector::iterator
之类的迭代器不会返回const
个值。
vector<int> v;
v.begin()++ = v.begin(); // pass compiler check
这有什么原因吗?
答案 0 :(得分:11)
我很确定这是因为它会对rvalue引用和任何类型的decltype
造成严重破坏。即使这些功能不在C ++ 03中,但已知它们即将到来。
更重要的是,我不相信任何标准函数返回const rvalues,它可能是在标准发布之后才考虑的事情。此外,const rvalues通常不被认为是Right Thing To Do™。并非所有非const成员函数的使用都是无效的,并且返回的const rvalues正在全面阻止它们。
例如,
auto it = ++vec.begin();
是完全有效的,实际上是有效的语义,如果不是完全可取的话。考虑我提供方法链的类。
class ILikeMethodChains {
public:
int i;
ILikeMethodChains& SetSomeInt(int param) {
i = param;
return *this;
}
};
ILikeMethodChains func() { ... }
ILikeMethodChains var = func().SetSomeInt(1);
这是否应该被禁止,因为有时候,我们可能会调用一个没有意义的功能?不,当然不。或者“互换化”怎么样?
std::string func() { return "Hello World!"; }
std::string s;
func().swap(s);
如果func()
生成了一个const表达式,这将是非法的 - 但它完全有效,实际上,假设std::string
的实现没有在默认构造函数中分配任何内存,快速和清晰/可读的。
你应该意识到C ++ 03 rvalue / lvalue规则坦率地说没有意义。它们实际上只是部分烘焙,并且在允许一些可能的权利的同时,最低限度要求禁止一些明显的错误。 C ++ 0x rvalue规则更加理智,而且完整。
答案 1 :(得分:1)
如果 it
是非常量的,我希望*(++it)
能够让我对它所代表的东西进行多变的访问。
但是,取消引用 [编辑:不,this is wrong too。我现在真的放弃了!] const
迭代器只会产生对它所代表的东西的非可变访问权。
这是我能想到的唯一原因。
正如您正确指出的那样,以下内容不正确,因为基元上的++
会产生一个右值(不能在LHS上):
int* p = 0;
(p++)++;
所以这里的语言确实存在一些不一致的地方。
答案 2 :(得分:-1)
编辑:这并没有真正回答评论中指出的问题。我会留下这里的帖子,无论如何它都很有用......
我认为这几乎是针对更好的可用界面的语法统一问题。当提供这样的成员函数而不区分名称并且只让重载解析机制确定正确的版本时,你阻止(或者至少试图)程序员做出const
相关的担忧。
我知道这似乎是矛盾的,特别是考虑到你的榜样。但是,如果您考虑大多数用例,那么它是有道理的。采用像std::equal
这样的STL算法。无论您的容器是否保持不变,您始终可以编写bool e = std::equal(c.begin(), c.end(), c2.begin())
之类的代码,而无需考虑正确版本的开头和结尾。
这是STL中的一般方法。记住operator[]
...考虑到容器将与算法一起使用,这是合理的。虽然在某些情况下您可能仍需要定义具有匹配版本(iterator
或const_iterator
)的迭代器,这一点也很明显。
嗯,这就是我现在想到的。我不确定它有多令人信服......
旁注:使用常量迭代器的正确方法是通过const_iterator
typedef。