• [Effective C++ --028]避免返回handles指向对象内部成分


    假设程序涉及矩形。每个矩形由其左上角和右下角表示。为了让Rectangle对象尽可能小,可能把定义矩形的点放在一个辅助的struct内再让Rectangle去指它:

     1 class Point {                      // 得到坐标
     2 public:
     3     Point(int x, int y) {};
     4     void setX(int newVal) {};
     5     void setY(int newVal) {};
     6 };
     7 
     8 struct RectData
     9 {
    10     Point ulhc;
    11     Point lrhc;
    12 };
    13 
    14 class Rectangle {
    15 public:
    16     Rectangle(Point p1, Point p2) {};
    17     Point& upperLeft() const {
    18         return pData->ulhc;
    19     }
    20     Point& lowerRight() const {
    21         return pData->lrhc;
    22     }
    23 private:
    24     std::tr1::shared_ptr<RectData> pData;
    25 };

    这样的设计可以通过编译,但却是错误的。实际上自相矛盾,一方面upperleft和lowerRight被声明为const,不让客户修改Rectangle。另一方面,这两个函数都返回reference指向private数据,调用者可以通过这些reference更改内部数据:

    1     Point c1(0, 0);
    2     Point c2(100, 100);
    3 
    4     const Rectangle rec(c1, c2);
    5     rec.upperLeft().setX(10);           // 现在rec变成从(50,0)到(100,100)

    这里需要注意:upperLeft的调用者能够使用被返回的reference(指向rec内部的Point成员变量)来更改成员。但rec其实应该是不可变的(const)!

    第一,成员变量的封装性最多只等于“返回其reference”的函数的访问级别

    第二,如果const成员函数传出一个reference,后者所指数据与对象自身有关,而它又被存储在对象之外,那么这个函数的调用者可以修改那笔数据。这正是bitwise constness的一个附带结果,条款3。

    如果它们返回的是指针或迭代器,相同的结果还会发生,原因相同。reference、指针和迭代器统统都是所谓的handles(号码牌,用来取得某个对象),而返回一个“代表对象内部数据的handle”,随之而来的便是“降低对象封装性”的风险。同时,也可能造成“虽然调用const成员函数却造成对象状态被更改”。

    通常我们认为,对象的“内部”就是指它的成员变量,其实不被公开使用的成员函数(protected或private)也是对象“内部”的一部分,所以也不该返回它们的handles。否则,它们的访问级别就会提高到返回它们的成员函数的访问级别。

    上述两个问题可以在它们的返回类型上加上const即可:

     1 class Rectangle {
     2 public:
     3     Rectangle(Point p1, Point p2) {};
     4     const Point& upperLeft() const {
     5         return pData->ulhc;
     6     }
     7     const Point& lowerRight() const {
     8         return pData->lrhc;
     9     }
    10 private:
    11     std::tr1::shared_ptr<RectData> pData;
    12 };

    即使这样,返回“代表对象内部”的handles,有可能在其他场合导致dangling handles(空悬的号码牌):这种handle所指东西(的所属对象)不复存在。这种“不复存在的对象”最常见的来源就是函数返回值。例如某个函数返回GUI对象的外框,这个外框采用矩形形式:

    1 class GUIObject{...}; 
    2 const Rectangle boundingBox(const GUIObject& obj);

    现在,客户有可能这么使用这个函数:

    1 GUIObject *pgo; 
    2 ... 
    3 const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());    //取得一个指针指向外框左上点

    对boundingBox的调用获得一个新的、暂时的Rectangle对象,这个对象没有名称,权且称它为temp。随后upperLeft作用于temp对象身上,返回reference指向temp的一个内部成分。具体指向temp的那个Point对象。但是这个语句结束之后,boundingBox的返回值,也就是我们所说的temp,将被销毁,而那间接导致temp内的Points析构。最终导致pUpperLeft指向一个不再存在的对象,变成空悬、虚吊(dangling)!

    只要handle被传出去了,不管这个handle是不是const,也不论返回handle的函数是不是const。这里的唯一关键是暴露在“handle比其所指对象更长寿”的风险下。

    ◆总结

    1.避免返回handles(包括reference、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生"虚吊号码牌(dangling handles)"的可能性降至最低。

  • 相关阅读:
    快的打车 技术部 在 杭州 招聘 #年前面试 年后入职#架构师
    王大锤_百度百科
    2013年总结
    泥沙俱下_百度百科
    thank you letter
    上海投行需要一大群JAVA,C++,C#,UNIX.走过路过不要错过!过完年想换工作看过来初级资深都有
    外省人员-办理护照_百度经验
    敬请贤者:WEB、IOS开发(2年以上经验,大专);CTO、产品经理,运营专员 电商服装鞋饰买手(2年以上经验,服装或鞋类);体验店店长 (2年以上经验,服装或鞋类) 工作地点:丰台南苑路;有意者小窗QQ2211788980
    “快的打车”创始人陈伟星的新项目招人啦,高薪急招Java服务端/Android/Ios 客户端研发工程师/ mysql DBA/ app市场推广专家,欢迎大家加入我们的团队!
    【深圳,武汉】一加科技(One Plus)招聘,寻找不...
  • 原文地址:https://www.cnblogs.com/hustcser/p/4213111.html
Copyright © 2020-2023  润新知