Programming 版 (精华区)

作者: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) = Po
int(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的成员函数中调用,比如说G
etArea().
9.既然这个迭代器不应该改变上述points_collection的状态,因此应该是const_iterat
or.
    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);
    }
--
(结束)

[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:202.268毫秒