简体中文版翻译:Alex
这是个好东西。意思是用const关键字来阻止const对象被修改。
例如,如果你要编写一个函数f(),它接收std::string类型的参数,并且想要对调用者保证不会修改调用者传过来的std::string参数,可以按以下方法声明f()
在传const引用和传const指针的情况下,任何试图在f()内部修改std::string的行为都会在编译时被编译器标记为错误。这完全是在编译时做的,所以使用const没有运行时的空间或速度损失。在传值时(f3()),被调用函数获得了调用者std::string的一份拷贝。也就是说,f3()可以修改这个拷贝,但返回时这个拷贝会被销毁。尤其是f3()无法修改调用者的std::string对象。
举个反例,如果想要编写一个函数g(),也是接收std::string,但想要告知调用者g()有可能会修改调用者的std::string对象。这时,可以按以下方法声明g():
在这些函数中省去const,就是告诉编译器允许(但不强制)它们修改调用者的std::string对象。因此,这些g()函数可以把它们的std::string传递给任何f()函数,但只有f3()(通过传值接收参数)能够将其参数传递给g1()或g2()。如果f1()或f2()需要调用g()函数,必须给g()传递一份std::string的本地拷贝。f1()或f2()的参数不能直接传递给g()函数。例如:
当然,在上面的例子中,任何g1()所做的修改都会反映到f1()函数内的localCopy对象。特别是,通过const引用传递给f1()的参数不会被修改。
[ Top | Bottom | Previous section | Next section ]
将参数声明为const正是另外一种形式的类型安全。这就好像const std::string是与std::string不同的类一样。因为const变量缺少一些非const变量所具有的一些变更性操作(例如,可以想象以下,const std::string没有赋值操作符)。
如果你发现普通的类型安全有助于构建正确的系统(的确有帮助,尤其是对于大型系统来说),你会发现const正确性也有帮助。
[ Top | Bottom | Previous section | Next section ]
应该在最最最开始。
事后保证const正确性会导致一种滚雪球效应:每次你在一个地方添加了const会需要在四个更多的地方也添加const。
[ Top | Bottom | Previous section | Next section ]
意思是p是一个指向Fred类的指针,但不能通过p来修改Fred对象(当然p也可以是NULL指针)。
例如,假设Fred类有一个叫做inspect()的const成员函数,那么写p->inspect()是可以的。但如果Fred类有一个非const成员函数mutate(),那么写p->mutate()就是个错误(编译器会捕获这种错误;不会在运行时测试;因此const不会降低运行速度)。
[ Top | Bottom | Previous section | Next section ]
应该从右往左读指针声明。
[ Top | Bottom | Previous section | Next section ]
意思是x是Fred对象的一个别名,但不能通过x来修改Fred对象。
例如,假设Fred类有一个叫做inspect()的const成员函数,那么写x.inspect()是可以的。但如果Fred类有一个非const成员函数mutate(),那么写x.mutate()就是个错误(编译器会捕获这种错误;不会在运行时检查;因此const不会降低运行速度)。
[ Top | Bottom | Previous section | Next section ]
没意义。
为了理解这个声明,需要从右往左读这个声明。因此“Fred& const x”的意思是“x是一个指向Fred的const引用”。但这是多余的,因为引用本来就是const的。你不能重新绑定一个引用。不管有没有const,都不行。
换句话说,“Fred& cosnt x”在功能上与“Fred& x”是一样的。因为在&后面加上const没什么用,因此为了避免迷惑就不应该多此一举。有人可能会认为这里加上const后指向的Fred对象就是const的了,就好像“const Fred& x”一样。
[ Top | Bottom | Previous section | Next section ]
“Fred cosnt& x”在功能上与const Fred& x相同。然而,真正的问题是应该用哪一种。
答案:绝没有任何人能够为你所在的机构做决定,除非他们了解你的机构。没有放之四海而皆准的规则。没有对所有机构都“正确”的答案。所以不要让任何人做仓促的选择。“思考(Think)”并非一个四字母的单词。
例如,一些机构看重的是一致性,并且已经有大量代码使用“const Fred&”了。对于他们来说,不管是否有优点,“Fred const&”都不是个好选择。还有很多其它的商业环境,一些倾向于“Fred const&”,其它则倾向于“const Fred&”。
采用适合你机构中普通维护程序员的写法。不是专家,不是傻瓜,而是维护代码的普通程序员。除非你决定解雇他们并雇佣新人,否则就要确保他们能够理解你的代码。根据实际情况做商业决定,而不是根据其它什么人的假设。
使用“Fred const&”需要克服一些惯性。现在大多数C++书籍都使用const Fred&,大多数程序员学C++时接触的就是这种语法,并且仍然这么用。这并非是说const Fred&一定对你的机构好。但在更改(这种风格)期间,和/或在招收新人时,的确可能会引起一些混乱。一些机构认为用Fred const&带来的好处更大,其它机构则不这么认为。
另一个警告:如果决定用Fred const&,确保采取措施使人们不会误写成没意义的“Fred& const x”。
[ Top | Bottom | Previous section | Next section ]
“Fred cosnt* x”在功能上与const Fred* x相同。然而,真正的问题是应该用哪一种。
答案:绝没有任何人能够为你所在的机构做决定,除非他们了解你的机构。没有放之四海而皆准的规则。没有对所有机构都“正确”的答案。所以不要让任何人做仓促的选择。“思考(Think)”并非一个四字母的单词。
例如,一些机构看重的是一致性,并且已经有大量代码使用“const Fred*”了。对于他们来说,不管是否有优点,“Fred const*”都不是个好选择。还有很多其它的商业环境,一些倾向于“Fred const*”,其它则倾向于“const Fred*”。
采用适合你机构中普通维护程序员的写法。不是专家,不是傻瓜,而是维护代码的普通程序员。除非你决定解雇他们并雇佣新人,否则就要确保他们能够理解你的代码。根据实际情况做商业决定,而不是根据其它什么人的假设。
使用“Fred const*”需要克服一些惯性。现在大多数C++书籍都使用const Fred*,大多数程序员学C++时接触的就是这种语法,并且仍然这么用。这并非是说const Fred*一定对你的机构好。但在更改(这种风格)期间,和/或在招收新人时,的确可能会引起一些混乱。一些机构认为用Fred const*带来的好处更大,其它机构则不这么认为。
另一个警告:如果决定用Fred const*,确保采取措施使人们不会误写成语义不同但语法相似的“Fred* const x”。这两者虽然第一眼看上去非常相似,但含义完全不同。
[ Top | Bottom | Previous section | Next section ]
是指仅查看(而不改变)对象的成员函数。
const成员函数会在紧跟函数参数列表的后面跟一个const关键字。有const后缀的成员函数被称作“const成员函数”或者是“查看函数”(inspector)。没有const后缀的成员函数被称作“非const函数”或“变更函数”(mutator)。
unchangeable.mutate()这个错误在编译期被发现。const不会有运行时的时空效率损失。
在inspect()成员函数后面的const后缀表示不会改变对象的(调用方可见的)抽象状态。这并非保证不改变对象的“底层二进制位”。C++编译器不允许将其解释为“按位”(不变),除非能解决别名问题,而别名问题一般无法解决(即可能存在会修改对象状态的非const别名)。另外一个对这种别名问题的(重要)认识是:用一根“指向const对象的指针”并不能保证对象不改变,它只是保证对象不会通过该指针被改变。
[ Top | Bottom | Previous section | Next section ]
如果想要从一个查看函数中返回this对象的引用,那么应该返回指向cosnst对象的引用,即const X&。
好消息是当你犯这种错误时,编译器通常能够发现。尤其是如果不小心返回了this对象的非const引用,例如上面的Person::name_evil(),编译器在编译这个成员函数时,通常能够发现并给出一条编译错误。
坏消息是编译器并不能发现所有这种错误:在一些情况下编译器无法产生一条错误消息。
最后:你需要思考,并记住本FAQ所述的原则。如果你通过引用返回的对象在逻辑上是this对象的一部分,而不管其是否在物理上放在了this 对象内,那么const方法应该返回const引用或直接按值返回。(this对象的“逻辑”部分与对象的“抽象状态”相关。请参阅前一个FAQ。)
[ Top | Bottom | Previous section | Next section ]
当一个查看函数和一个变更函数名字相同,且参数个数与类型也相同时就有用了——即两者的不同之处仅在于一个有const另一个没有const。
const重载的一个常见应用是下标运算符。通常应该尽量使用标准模板容器,例如std::vector,但有时会需要在自己的类中支持下标运算符。一个经验法则是:下标运算符通常成对出现。
当对一个非const的MyFredList对象使用下标运算符时,编译器会调用非const的下表运算符。因为返回的是一个普通Fred&,所以能够查看或修改对应的Fred对象。例如,假设Fred类有一个查看函数Fred::inspect() const和一个变更函数Fred::mutate():
但是,当对一个const的MyFredList对象使用下标运算符时,编译器会调用const的下标运算符。因为会返回const Fred&,所以可以查看对应的Fred对象而不能修改它。
在以下FAQ中演示了针对下标运算符和函数调用运算符的const重载:[13.10], [16.17], [16.18], [16.19]和[35.2]
当然除了下标运算符,其它函数也可以进行const重载。
[ Top | Bottom | Previous section | Next section ]
用mutable(或者实在没办法了,用最后一招const_cast)
少数查看函数需要对数据成员做适当的修改(例如,一个Set对象可能想要缓存上一次查看的内容,以便下一次查看时能够提高性能)。这里“适当”的意思是,所做的修改不会从对象的接口上反映到外部(否则该成员函数就应该是一个变更函数,而不是查看函数了)。
这时,需要修改的数据成员应标记为mutable(把mutable关键字放在数据成员的声明前;即和const的位置一样)。这就通知编译器说这个数据成员允许在const成员函数中被修改。如果编译器不支持mutable关键字,那么可以通过const_cast去除掉this的const(但是记着读下面的注意事项)。例如在Set::lookup() const中,可以这么写:
然后,self和this内容一样(即self == this为真),但self类型是Set*而不是const Set*(技术上来讲,是const Set* const,不过最右边的const与这里的问题无关)。因此可以使用self来修改this所指向的对象。
注意:const_cast可能会导致一种非常罕见的错误。这个错误仅在三件很少见的事情同时发生时出现:数据成员本应该是mutable(例如上面所说的),编译器不支持mutable,并且对象原本就定义为const(不是通过一根指向const对象的指针来访问的普通const对象)。虽然这种组合非常罕见,甚至永远不会发生,但如果真的发生了,那么这种代码可能就不能正常运行(标准说这种行为是未定义的)。
如果想要用const_cast,那么应该用mutable替代。换句话说,如果需要修改一个对象的成员,而又是通过指向const对象的指针来访问这个对象,那么最安全和最简单的做法就是给该成员的声明前加上mutable。如果你确信实际对象不是const的(例如能够确定对象是像这样声明的:Set s;),那么也可以用const_cast。但如果对象本身就是const的(例如可能声明为:const Set s;),那么就应该用mutable而不是const_cast。
请不要告诉我说Y编译器的X版本在Z机器上允许修改const对象的非mutable成员。我不管这个——根据标准这是错误的,如果换一个编译器,甚至是同一编译器的不同版本(升级版),你的代码就可能会失败。不要这么做。用mutable吧。
[ Top | Bottom | Previous section | Next section ]
在理论上是的;在实际中不会。
即使语言本身禁止了const_cast,要想在调用const成员函数时避免读写寄存器的唯一办法是解决别名问题(即要证明没有其它指向该对象的非const指针)。这只有在极少数情况下才能办到(当在调用const成员函数时构造对象,所有在构造对象和调用const成员函数之间调用的非const成员函数是静态绑定的,并且所有这些调用包括构造函数都是内联的,同时构造函数调用的任何成员函数也要是内联的)。
[ Top | Bottom | Previous section | Next section ]
因为“const int* p”意思是“p保证不会修改*p”,而不是说“*p保证不变”。
用const int*指向一个int,不会使这个int变为const。int不会通过const int*被修改,但如果有另外一个int*(注意没有const)指向该int(这就是“别名”),那么这个int*指针可以用来修改int。例如:
注意main()和f(const int*, int*)可能是在不同的编译单元中,并且不是在同一天编译的。因此,编译器就无法在编译时发现别名。因此无法在语言中禁止这种事情。实际上,我们甚至不想添加这样一个规则。因为一般来说,允许很多指针指向同一个对象,这是一个功能。当指针保证说不去修改所指内容时,这只是该指针作出的保证,而不是内容所做的保证。
[ Top | Bottom | Previous section | Next section ]
不是!(这个与int指针的别名问题相关)
“const Fred* p”意思是不能通过指针p来修改Fred,但有可能不经过const(例如一个非const指针Fred*),而是通过其它途径来访问object。例如,如果有两根指针“const Fred* p”和“Fred* q”都指向同一个Fred对象(别名),那么指针q可以用来修改Fred对象,但指针p不能。
[ Top | Bottom | Previous section | Next section ]
因为把Foo**转换成const Foo**是非法且危险的。
C++允许Foo*到const Foo*的转换(这是安全的)。但如果想要将Foo**隐式转换成const Foo**则会报错。
这么做的原因如下所示。但首先,这里有个最普通的解决办法:只要把const Foo**改成const Foo* const*就可以了。
之所以Foo**到const Foo**的转换是危险的,是因为这会使你没有经过转换就在不经意间修改了const Foo对象。
记住:请不要用指针转换绕过这里。别这么做就是了!
[ Top | Bottom | Previous section | Next section ]
E-mail the author
[ C++ FAQ Lite
| Table of contents
| Subject index
| About the author
| ©
| Download your own copy ]
Revised Jan 2, 2009