Guru of the Week 条款06:正确使用const

原创 2001年10月23日 20:12:00
 

GotW #06 Const-Correctness

著者:Herb Sutter     

翻译:kingofark

[声明]:本文内容取自www.gotw.ca网站上的Guru of the Week栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。

Revision 1.0

Guru of the Week 条款06:正确使用const<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

难度:6 / 10

 

(总是尽可能的使用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 ) // 如果还没有被计算和保存,

          CalcArea();     // 那么现在就开始。

        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 ) // 连接

        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.       1const值传递没有多大意义,反而容易引起人误解。

 

3.       这个成员函数应该定义成一个const成员函数,因为它不改变对象的状态。

 

4.       (本要点是一个有争议的提法)如果该函数的返回类型不是一个内建(built-in)类型的话,通常应该将其返回类型也声明为const。这样做有利于该函数的调用者,因为它使得编译器能够在函数调用者企图修改临时量的时候产生一个错误信息,从而达到保护目的(例如, Poly.GetPoint(i) = Point(2, 2);”,等等。诚然,如果真的想要修改临时量,那么首先就应该让GetPoint()返回引用(return-by-reference)而不是返回值(return-by-value)。然而在后面的叙述中我们又将看到,在用于operator+()中的const Polygon对象时,让GetPoint()返回const值或者const引用又是很有用的。)。

 

[作者记Lakos(pg.618)对返回const值提出异议。他认为,对于内建(built-in)类型也返回const值不但没有什么实际意义的,反而还会影响模板的实体化过程(instantiation)。]

 

[学习指导]:在函数内对非内建(non-built-in)的类型采用值返回(return-by-value)的方法时,最好让函数返回一个const值。

 

 

*     int GetNumPoints() {

        return points_.size();

      }

 

5.       还是那句话,函数本身应该被声明为const

 

(注意在这里不应该返回const int,因为int本身已经是一个右值类型;加上const以后反而会影响模板的实体化过程(instantiation),使代码变得容易让人糊涂,引人误解,甚至让人消化不良也有可能。

 

 

*     double GetArea() {

        if( area_ < 0 ) //如果还没有被计算和保存,

          CalcArea();     //那么现在就开始。

        return area_;

        }

 

6.       尽管这个函数修改了对象的内部状态,但它还是应该被声明为const,因为被修改对象的可见(observable)状态没有发生变化(我们所做的是一些隐蔽的、不可告人的事情,但这是一个实现中的细节,即这个对象从逻辑上讲还是const)。这意味着,area_应该被声明成mutable。如果你的编译器目前还不支持mutable关键字的话,可以对area_采取const_cast操作以作为替代方案(最好还能在这里写一个注释,以便在可以使用mutable关键字的时候把这个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.       既然iterator不会改变points_集的状态,所以这里应该是一个const_iterator

 

 

*     vector<Point> points_;

      double        area_;

    };

 

    Polygon operator+( Polygon& lhs, Polygon& rhs ) {

 

10.   显然,这里应该通过const引用(reference)进行传递。

 

11.   还是那句话,返回值应该是const的。

 

 

*     Polygon ret = lhs;

      int last = rhs.GetNumPoints();

 

12.   既然“last”也从不会被改变,那么也应该为“const int”型。

 

(这也是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毫无作用,因为无论如何一个引用(reference)是不可能被改变,使其指向另一个对象的。

 

 

*   void h( Polygon* const pPoly ) {

      pPoly->AddPoint( Point(2,2) );

    }

 

14.   这里的const也不起作用,但其原因与13中的不同:这次是因为你对指针使用传值方式,这与上面讲到的传递一个const int参数一样毫无意义。

 

(如果你在对附加题的解答中提到这里的函数会产生编译错误的话……不好意思,它们是合法的C++用法。也许你还考虑过把const放在&或者*的左边,但那只会使函数体本身不符合C++用法。)

 

 

 

*   int main() {

      Polygon poly;

      const Polygon cpoly;

      f(poly);

 

这儿很好,没什么问题。

 

 

*     f(cpoly);

 

在这里,当f()企图放弃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 ) // 如果还没有进行计算和保存,

                CalcArea();     //那么现在就开始。

            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 ) // 连接

            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);

    }

 

判断给定的时间是否满足表达式

/** * 判断给定的时间 是否 满足 crontab 表达式【忽略毫秒】 * // * * 7 * * ? * * @param date the ...
  • yibing548
  • yibing548
  • 2016年05月20日 11:37
  • 2033

C++语法总结,语法查询

How to Program in C++ http://cs.fit.edu/~mmahoney/cse2050/how2cpp.html How to Program ...
  • sergery
  • sergery
  • 2013年06月28日 11:38
  • 54594

使用Calendar获取DAY_OF_WEEK本周开始和结束的时间戳

和获取今日开始和结束的时间戳一样,本周开始和结束的时间戳,就是以本周第一天开始的00:00到本周最后一天的23:59分结束。 需要注意的是,国际上是以星期日为一周第一天的开始,Calendar中提供...
  • zhuwentao2150
  • zhuwentao2150
  • 2016年08月01日 12:56
  • 8841

cheshire cat/pimpl idiom & Guru of the Week 条款28:“Fast Pimpl”技术

PIMPL是C++开发中经常使用的一种惯用法,其原理主要是将对定义的依赖转换为对声明的依赖,通过前向声明,达到接口与实现的分离的效果,并将编译时文件间的依赖降到最低,从而大大缩短程序编译的时间。 P...
  • tianalotus
  • tianalotus
  • 2013年06月21日 17:41
  • 701

读书笔记《Effective C++》条款06:若不想使用编译器自动生成的函数,就该明确拒绝

所有编译器产出的函数都是public。如果不想让编译器自动生成这些函数,得自行声明它们,可以将copy构造函数或copy assignment操作符声明为private,这样就阻止了编译器暗自创建的版...
  • u014558668
  • u014558668
  • 2017年05月04日 00:22
  • 212

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

// 条款06: 若不想使用编译器自动生成的函数,就该明确拒绝。 #include // 防止编译器自动生成copy构造函数和copy assignment操作符的两种方法: // 1.将这两个...
  • u011726005
  • u011726005
  • 2017年08月06日 18:51
  • 78

Effective C++--条款03:尽可能使用const

如果const出现在*左端,表示被指物为常量,如果const出现在*右端,说明指针自身为常量。 例如: int b = 9; int a = 88; const int* p1...
  • u011389977
  • u011389977
  • 2016年06月14日 13:55
  • 194

条款03:尽可能使用const

结论1:将某些东西声明为const可帮助编译器侦测出错误用法。const可被
  • u013540854
  • u013540854
  • 2014年05月28日 01:26
  • 387

Effective C++学习笔记——条款03:尽可能使用const

今天开始学习条款三,详细解读一下,让自己有更大的收获。   今天开始学习条款三,详细解读一下,让自己有更大的收获。   const 多才多艺,可以用在classes外部修饰global和name...
  • xiexievv
  • xiexievv
  • 2011年09月12日 11:38
  • 1191

条款03 尽可能使用const

const允许指定一个语义约束(也就是指定一个“不该被改动”的对象),而编译器会强制实施这项约束。它允许你告诉编译器和其他程序员某值应该保持不变。 const可以用在classes外部修饰glob...
  • wangdd_199326
  • wangdd_199326
  • 2017年12月13日 17:15
  • 22
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Guru of the Week 条款06:正确使用const
举报原因:
原因补充:

(最多只允许输入30个字)