GotW #6: const的正确用法

原文在http://www.gotw.ca/gotw/006.htm
看过Exceptional C++的可以跳过。
难度:6/10
尽量使用const,但别太过头了。这儿有个使用const的例子,有些是明显或不明显应该用const的,有些则是不该用const的。
问题:
const能帮你写出更安全的代码,也能帮助编译器优化。所以你应该尽量的使用const。但是尽量是多少呢?
别改变下面这段代码的结构,它是纯粹拼凑出来的。只能增加或删去const和一些变量。
加分题:找出代码中由于const的错误使用从而导致未定义的运行结果或者编译出错的地方。
    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();
      
forint 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.因为pt是值传递的,所以const没什么用。
Point GetPoint( const int i ) {
return points_[i];
}
2.同上。通常值传递时const是没用的。
3.应该是const成员函数。因为GetPoint()没改变对象的状态。
4.(存争议)返回非内部类型的值时,通常应为const。这样当调用者试图修改这个临时对象的值时,编译器会报错。比如,poly.GetPoint(i)=Point(2,2);毕竟要让它运行,GetPoint()应该返回一个引用,而不是值。过一会还将看到GetPoint()返回常量或者const引用是有用处的,因为operator+()里它将要被用作const对象。
[备忘]:Lakos(pg.618)反对返回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_应该声明为mutable。如果你的编译器不支持mutable,那你就需要用const_cast处理area_。(最好在这里加上注释,下次编译器支持mutable后,及时改成mutable),但别忘了必须把函数声明成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,毕竟它会被GetArea()这样的const成员函数调用。
9.因为iterator不应该会修改points_ 向量的状态,所以应用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放在&或*的左边,那倒是会编译错误的。)
int main() {
Polygon poly;
const Polygon cpoly;
f(poly);
没问题。
f(cpoly);
这里就会出现未定义的结果。f()会去掉cpoly的const限制,然后修改它。
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);
}

搞定。中间浏览器挂了一次,结果损失了2个小时的活儿。。现在学乖了,写几段就保存一次。

posted on 2012-02-22 17:27 高兴 阅读(223) 评论(0)  编辑 收藏 引用 所属分类: GotW


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


导航

<2012年2月>
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

统计

常用链接

留言簿

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜