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

翻译文档 专栏收录该内容
74 篇文章 0 订阅
 

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

 

难度: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);
  
    }
  

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
<p> <span style="font-size:14px;color:#337FE5;">【为什么学爬虫?】</span> </p> <p> <span style="font-size:14px;">       1、爬虫入手容易,但是深入较难,如何写出高效率的爬虫,如何写出灵活性高可扩展的爬虫都是一项技术活。另外在爬虫过程中,经常容易遇到被反爬虫,比如字体反爬、IP识别、验证码等,如何层层攻克难点拿到想要的数据,这门课程,你都能学到!</span> </p> <p> <span style="font-size:14px;">       2、如果是作为一个其他行业的开发者,比如app开发,web开发,学习爬虫能让你加强对技术的认知,能够开发出更加安全的软件和网站</span> </p> <p> <br /> </p> <span style="font-size:14px;color:#337FE5;">【课程设计】</span> <p class="ql-long-10663260"> <span> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 一个完整的爬虫程序,无论大小,总体来说可以分成三个步骤,分别是 </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 网络请求模拟浏览器的行为从网上抓取数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据解析将请求下来的数据进行过滤,提取我们想要的数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据存储将提取到的数据存储到硬盘或者内存中。比如用mysql数据库或者redis等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 那么本课程也是按照这几个步骤循序渐进的进行讲解,带领学生完整的掌握每个步骤的技术。另外,因为爬虫的多样性,在爬取的过程中可能会发生被反爬、效率低下等。因此我们又增加了两个章节用来提高爬虫程序的灵活性,分别是 </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 爬虫进阶包括IP代理,多线程爬虫,图形验证码识别、JS加密解密、动态网页爬虫、字体反爬识别等。 </li> <li class="" style="font-size:11pt;color:#494949;"> Scrapy和分布式爬虫Scrapy框架、Scrapy-redis组件、分布式爬虫等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 通过爬虫进阶的知识点我们能应付大量的反爬网站,而Scrapy框架作为一个专业的爬虫框架,使用他可以快速提高我们编写爬虫程序的效率和速度。另外如果一台机器不能满足你的需求,我们可以用分布式爬虫让多台机器帮助你快速爬取数据。 </p> <p style="font-size:11pt;color:#494949;">   </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 从基础爬虫到商业化应用爬虫,本套课程满足您的所有需求! </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;background-color:#FFFFFF;color:#337FE5;">【课程服务】</span> </p> <p> <span style="font-size:14px;">专属付费社群+定期答疑</span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"><br /> </span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"></span> </p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值