作者:Hub Sutter
译者:plpliuly
/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第6篇,原文的版权是属于Hub Sutter(著名的C++专家,"Exceptional C++"的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者
*/
#6 正确使用const (1997年5月21日提出)
难度:6/10
尽可能的使用const,但是不要滥用.我们将讨论几处很明显的和几处并不明显的应该或不应该用const地方.(译者:是不是太拗口了?没办法,我的翻译水平实在有限,各位看官担待了:-))
问题:
const是写出更安全的代码的一个利器.而且它还可以帮助编译器进行优化.应该尽可能的多用...但是什么才叫做"尽可能"呢?
不对下面程序的结构和其他风格问题作挑剔,因为它仅仅是用来做示例说明.请只在合适的地方简单的加上或删除"const"(包括一些变量和相关的关键字).附加题是:哪些地方将使程序由于const的错误使用而产生编译错误或不确定的(undefined)结果?
class Polygon {
public:
Polygon() : area_(-1) {}
void AddPoint( const Point pt ) {
InvalidateArea();
points_.push_back(pt);
}
Point GetPoint( const int i ) {
return points_[i];
}
int GetNumPoints() {
return points_.size();
}
double GetArea() {
if( area_ < 0 ) // if not yet calculated and cached
CalcArea(); // calculate now
return area_;
}
private:
void InvalidateArea() { area_ = -1; }
void CalcArea() {
area_ = 0;
vector<Point>::iterator i;
for( i = points_.begin(); i != points_.end(); ++i )
area_ += /* some work */;
}
vector<Point> points_;
double area_;
};
Polygon operator+( Polygon& lhs, Polygon& rhs ) {
Polygon ret = lhs;
int last = rhs.GetNumPoints();
for( int i = 0; i < last; ++i ) // concatenate
ret.AddPoint( rhs.GetPoint(i) );
return ret;
}
void f( const Polygon& poly ) {
const_cast<Polygon&>(poly).AddPoint( Point(0,0) );
}
void g( Polygon& const rPoly ) {
rPoly.AddPoint( Point(1,1) );
}
void h( Polygon* const pPoly ) {
pPoly->AddPoint( Point(2,2) );
}
int main() {
Polygon poly;
const Polygon cpoly;
f(poly);
f(cpoly);
g(poly);
h(&poly);
}
答案:
class Polygon {
public:
Polygon() : area_(-1) {}
void AddPoint( const Point pt ) {
InvalidateArea();
points_.push_back(pt);
}
1.既然point对象是值拷贝方式传递的,声明为const没有多大的意义.
Point GetPoint( const int i ) {
return points_[i];
}
2.同上面一样.const的传值参数通常是没什么用处的,而且只会容易引起误解.
3.此处应该是一个const的成员函数,因为它并没有改变对象的状态.
4.(有争议)对于非原生类型(non-builtin)的返回值拷贝通常应该是const的.这会有利于调用该函数的代码通过编译器防止修改返回的临时对象(比如,"poly.GetPoint(i) = Point(2,2);"...如果真想这么做,GetPoint应该返回对象的引用,而不是通过传值方式返回临时对象上.我们在后面将要看到,让GetPoint返回const类型或const引用类型是很有意义的,因为这会在operator+()中对于const的Polygon对象的处理很有用)
注意:Lakos反对返回const类型,他认为这样做会妨碍模板的实例化.不过值得注意的是:对于原生类型来讲,确实是没有必要返回const类型的(比如返回"const int").
[忠告]:对于非原生类型的传值返回,尽量返回一个const的类型值.
int GetNumPoints() {
return points_.size();
}
5.这个函数也应该是const.
(此处就不应该返回const int,因为int已经是一个右值,加上const会妨碍模板的实例化,而且容易使人迷惑,产生误解)
double GetArea() {
if( area_ < 0 ) // if not yet calculated and cached
CalcArea(); // calculate now
return area_;
}
6.尽管这个函数改变了对象的内部状态,但它应该是const.因为对象的可观察的状态没有改变(我们做的让area_保存计算结果只是一个实现细节,从逻辑上讲对象应该是const的).这意味着area_应该声明为mutable,如果你的编译器不支持mutable,就将就对area_进行const_cast来代替(建议当编译器支持mutable后去掉这个const_cast),而把函数改为const函数.
private:
void InvalidateArea() { area_ = -1; }
7.此处是可争议的,但我还是建议把这个函数设为const,这样做只是为了一致性.(从语义上讲,这个函数只会在非const的函数中调用,因为它的目的就是在对象的状态改变时使得area_保存的面积值无效)
void CalcArea() {
area_ = 0;
vector<Point>::iterator i;
for( i = points_.begin(); i != points_.end(); ++i )
area_ += /* some work */;
}
8.这个成员函数肯定应该是const.毕竟,它会在另一个const的成员函数中调用,比如说GetArea().
9.既然这个迭代器不应该改变上述points_collection的状态,因此应该是const_iterator.
vector<Point> points_;
double area_;
};
Polygon operator+( Polygon& lhs, Polygon& rhs ) {
10.当然应该是传递const引用作为参数.
11.返回类型也应该是const的.
Polygon ret = lhs;
int last = rhs.GetNumPoints();
12.既然"last"不需改变,那就声明为"const int"类型.
for( int i = 0; i < last; ++i ) // concatenate
ret.AddPoint( rhs.GetPoint(i) );
(GetPoint()应该声明为const成员函数,而且返回const类型或const引用类型的另一个原因)
return ret;
}
void f( const Polygon& poly ) {
const_cast<Polygon&>(poly).AddPoint( Point(0,0) );
附件问题的答案:如果被引用的对象声明为const的(就如下面的f(cpoly)),这个结果就是不确定的.这个参数不是一个真正的const,因此不要声明为const!
}
void g( Polygon& const rPoly ) {
rPoly.AddPoint( Point(1,1) );
}
13.此处的"const"是没有用处的,因为引用不可能被更改去引用另一个不同对象.[译者的题外话:引用和被其引用的对象的关系是在引用初始化时确定的,此后不会发生变化.因此引用变量也必须在声明的时候初始化,对于引用类型的类成员变量也必须通过构造函数的初始化序列进行初始化.]
void h( Polygon* const pPoly ) {
pPoly->AddPoint( Point(2,2) );
}
14.这个"const"同样也是多余的,但是原因与上面不同:因为你传递的是一个指针的值,这种写法和上面传递"const int"没有多大的区别.
(如果你认为附加题的答案是此处会引起编译器错误,那么,对不起,这是一个很合法的C++写法.你或许在想应该把"const"移到&或*的左边,但那会使函数体中发生编译错误[译者:移到&或*左边的const是指引用或指针指向的对象应该是const的,上面代码中的const意义是指引用或指针自身是const的])
int main() {
Polygon poly;
const Polygon cpoly;
f(poly);
此处没错.
f(cpoly);
此处如果f()试图将const属性cast掉,然后改变参数值,将会产生一个不确定的结果.
g(poly);
此处没问题.
h(&poly);
此处没问题.
}
好了,我们最后可以如下得到经过修改后的版本(记住,只是修改了const,不是所有的风格问题都修改了):
class Polygon {
public:
Polygon() : area_(-1) {}
void AddPoint( Point pt ) { InvalidateArea();
points_.push_back(pt); }
const Point GetPoint( int i ) const { return points_[i]; }
int GetNumPoints() const { return points_.size(); }
double GetArea() const {
if( area_ < 0 ) // if not yet calculated and cached
CalcArea(); // calculate now
return area_;
}
private:
void InvalidateArea() const { area_ = -1; }
void CalcArea() const {
area_ = 0;
vector<Point>::const_iterator i;
for( i = points_.begin(); i != points_.end(); ++i )
area_ += /* some work */;
}
vector<Point> points_;
mutable double area_;
};
const Polygon operator+( const Polygon& lhs,
const Polygon& rhs ) {
Polygon ret = lhs;
const int last = rhs.GetNumPoints();
for( int i = 0; i < last; ++i ) // concatenate
ret.AddPoint( rhs.GetPoint(i) );
return ret;
}
void f( Polygon& poly ) {
poly.AddPoint( Point(0,0) );
}
void g( Polygon& rPoly ) { rPoly.AddPoint( Point(1,1) ); }
void h( Polygon* pPoly ) { pPoly->AddPoint( Point(2,2) ); }
int main() {
Polygon poly;
f(poly);
g(poly);
h(&poly);
}
--
(结束)