Guru of the Week #6:正确使用const

作者: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);
    }
--
(结束)

  • 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官方博客 返回首页

打赏

plpliuly

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值