zoukankan      html  css  js  c++  java
  • [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)"的可能性降至最低。

  • 相关阅读:
    Swift入门篇-Hello World
    Swift入门篇-swift简介
    Minecraft 插件 world edit 的cs 命令
    搭建本地MAVEN NEXUS 服务
    MC java 远程调试 plugin 开发
    企业内部从零开始安装docker hadoop 提纲
    javascript 命令方式 测试例子
    ca des key crt scr
    JSF 抽象和实现例子 (函数和属性)
    form 上传 html 代码
  • 原文地址:https://www.cnblogs.com/hustcser/p/4213111.html
Copyright © 2011-2022 走看看