Effective c++ 条款28:避免返回handles指向对象内部成分

假设有这样一个类,它用来描述一个矩形,其私有变量为一个智能指针,指向一个含有点坐标信息的结构RectData,其结构内含有两个Point结构类,用于表示左上角和右下角的点坐标。
这个类内有这样两个函数,用于获取左上角和右下角坐标:

class Rectangle {
    public:
    ...
    Point& upperLeft() const { return pData->ulhc; }
    Point& lowerRight() const { return pData->lrhc; }
    ...
    private:
        std::tr1::shared_ptr<RectData> pData;
};

1、留心不要返回对象的内部的handles

根据以前的条款给我们的忠告,以引用方式传递用户自定义类型往往比值传递方式更高效,然而上述的设计实际上是自我矛盾的。
一方面upperLeft()和lowerRight()被声明为const成员函数,因为它们的目的只是为了提供客户一个得知Rectangle相关坐标点的方法,而不是让客户修改;另一方面两个函数却都返回引用指向private内部数据,调用者于是可以通过这些引用更改内部数据!
这立刻带给我们两个教训。
第一,成员变量的封装性最多只等于“返回其reference”的函数的访问级别。本例之中虽然pData->ulhc和pData->lrhc都被声明为private,但是它们实际上却是public,因为函数传出了它们的引用。
第二,如果const成员函数传出一个引用,后者所指数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据,这正是bitwise constness的一个附带结果。

上面我们所说的每件事情都是由于“成员函数返回引用”。如果它们返回的是指针或迭代器,相同的情况还是发生,原因也相同。引用、指针和迭代器统统都是所谓的handles(号码牌,用来取得某个对象),而返回一个“代表对象内部数据”的handle,随之而来的便是“降低对象封装性”的风险。同时,它也可能导致‘’虽然调用const成员函数却造成对象状态被更改。
通常我们认为,对象的内部就是指它的成员变量,但其实不被公开使用的成员函数也是对象的内部的一部分。因此,你绝对不该令成员函数返回一个指针指向访问级别较低“的成员函数”。然而这种情况并不多见。
要解决前面返回引用的问题,只要对它们的返回类型加上const即可。但即使如此,upperLeft和 lowerRight还是返回了“代表对象内部”的handles,有可能在其他场合带来问题。更明确地说,它可能导致dangling handles(空悬的号码牌)。

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

GUIObject* pgo;
...
const Point* pUpperLeft = &(boudingBox(*pgo).upperLeft());

对boundingBox的调用获得一个新的、暂时的Rectangle对象。这个对象没有名称,所以我们权且称它为temp。随后upperLeft作用于temp身上,返回一个reference指向temp的一个内部成分,即用以标示temp的Points。于是pUpperLeft指向那个Point对象。而在这个语句结束之后,boundingBox的返回值,也就是我们所说的temp,将被销毁,而那间接导致temp内的Points析构最终导致pUpperLeft指向一个不再存在的对象
这就是为什么函数如果返回一个handle代表对象内部成分总是危险的原因。只要有一个handle被传出去了,一旦如此你就是暴露在“handle比起所指对象更长寿”的风险。
这并不意味你绝对不可以让成员函数返回handle,有时候你必须这么做。例如operator[]就允许你返回references指向容器内的数据,那些数据会随着容器被销毁而销毁。

猜你喜欢

转载自blog.csdn.net/unirrrrr/article/details/81329704