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

一. 内容

  1. 举个例子,假如你的程序涉及矩形,每个矩形由左上角和右下角坐标表示,然后采用 pimpl 手法,以及之前学过的条款知识。

    namespace RectangleStuff {
        class Point {
        public:
            Point(int mX, int mY): X(mX), Y(mY) {}
        public:
            void SetX(int mX) {
                X = mX;
            }
    
            int GetX() {
                return X;
            }
    
            void SetY(int mY) {
                Y = mY;
            }
    
            int GetY() {
                return Y;
            }
    
        private:
            int X;
            int Y;
        };
    
        struct RectangleData {
            Point LeftUp;
            Point RightDown;
        };
    
        class Rectangle {
        public:
            Rectangle(const Point& mLeftUp, const Point& mRightDown): Data(new RectangleData({mLeftUp, mRightDown})) { }
    
        public:
            Point& GetLeftUp() const {
                return Data->LeftUp;
            }
    
            Point& GetRightDown() const {
                return Data->RightDown;
            }
    
        private:
            std::shared_ptr<RectangleData> Data;
        };
    
        inline void Try() {
            const Point CoordOne(0, 100);
            const Point CoordTwo(100, 0);
            const Rectangle Rectangle(CoordOne, CoordTwo);
            std::cout << Rectangle.GetLeftUp().GetX() << "\n";
            std::cout << Rectangle.GetRightDown().GetX() << "\n";
    
            Rectangle.GetLeftUp().SetX(1);
            std::cout << Rectangle.GetLeftUp().GetX() << "\n";
        }
    }
    
  2. 注意 明明在 Try 函数中我们定义的 const 变量,但在之后的语句中我们可以轻易修改 Rectangle 中的成员变量,观察 Rectangle 的 Getter 函数实现,发现我们返回的是内部数据的引用,那些 Point 变量的引用,调用函数者便可借机修改它们。

  3. 这立刻带给我们两个教训:

    • 成员变量的封装性最多等于返回其引用的函数的访问级别。本例中的 Data 声明为 private,但它们实际上却是 public。
    • 如果 const 成员函数返回一个引用,那么实际上调用者是可以修改其中的数据的。这是 bitwise constness 的附带结果。
  4. 上述所说的每一件事情,对于指针或迭代器同样适用,原因也相同。引用,指针,迭代器统统都是所谓的 handles,用来取到某个对象。而返回一个代表内部数据的 handle,随之而来的便是降低对象封装性的风险。同时,一如所见,它也可以导致虽然调用 const 成员函数却造成对象被修改的结果。

  5. 也许你可以说加上常量性的 handles。但尽管如此,仍然会带来一些问题。更明确的说,它们会导致空悬的 handles:这种 handles 所指东西不复存在。比如对于 Rectangle 的 GetLeftUp 函数调用会返回一个 内部数据LeftUp 的 const-reference,我们将返回值保存在一个缓存对象中,如果此时 Rectangle 是一个匿名对象,该语句执行完会自动析构,那么缓存对象所保存的 const-reference 实际上已经不存在任何事物了。这就是为什么函数如果返回一个 handle 代表对象内部成分总是危险的原因。

  6. 当然这并不意味着绝对不可以让成员函数返回 handle。有时候必须那样做,例如 operator[ ]就允许你使用 strings 或 vectors 内的成员,返回它们的引用。但这些函数是例外,并不是常态。

二. 总结

  1. 避免返回 handles,包括 references,指针,迭代器指向对象内部,遵守这个条约可增加封装性,帮助 const 成员函数的行为像个 const,并将发生虚吊号码牌(dangling handles)的可能性降到最低。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值