• Effective C++ 学习笔记 条款22 将成员变量声明为private


    下面是作者的规划。首先带你看看为什么成员变量不该是public,然后让你看看所有反对public成员变量的论点同样适用于protected成员变量。最后导出一个结论:成员变量应该是private。获得这个结论后,本条款也就大功告成了。

    好,现在看看public成员变量。为什么不采用它呢?

    让我们从语法一致性开始(同时请见条款18)。如果成员变量不是public,客户唯一能够访问对象的办法就是通过成员函数。如果public接口内的每样东西都是函数,客户就不需要在打算访问class成员时迷惑地试着记住是否该用小括号(圆括号)。他们只要做就是了,因为每样东西都是函数。就生命而言,这至少可以省下许多搔首弄耳的时间。

    或许你不认为一致性的理由足以令人信服,那么这是事实如何:使用函数可以让你对成员变量的处理有更精确的控制。如果你令成员变量为public,每个人都可以读写它,但如果你以函数取得或设定其值,你就可以实现出“不准访问”、“只读访问”、“读写访问”。你甚至可以实现“唯写访问”,如果你想要的话:

    class AccessLevels
    {
    public:
        // ...
        int getReadOnly() const { return readOnly; }
        void setReadWrite(int value) { readWrite = value; }
        int getReadWrite() const { return readWrite; }
        void setWriteOnly(int value) { writeOnly = value; }
    
    private:
        int noAccess;    // 对此int无任何访问动作
        int readOnly;    // 对此int只做访问(read-only access)
        int readWrite;    // 对此int做读写访问(read-write access)
        int writeOnly;    // 对此int做惟写访问(write-only access)
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如此细微地划分访问控制颇有必要,因为许多成员变量应该被隐藏起来。每个成员变量都需要一个getter函数和setter函数毕竟罕见。

    还是不够说服你?是端出大口径武器的时候了:封装。如果你通过函数访问成员变量,日后可改以某个计算替换这个成员变量,而class客户一点也不会知道class的内部实现已经起了变化。

    举个例子,假设你正在写一个自动测速程序,当汽车通过,其速度便被计算并填入一个速度收集器内:

    class SpeedDataCollection
    {
    // ...
    public:
       void addValue(int speed);    // 添加一笔新数据
       double averageSoFar() const;    // 返回平均速度
       // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    现在让我们考虑成员函数averageSoFar。做法之一是在class内设计一个成员变量,记录至今以来所有速度的平均值。当averageSoFar被调用,只需返回那个成员变量就好。另一个做法是令averageSoFar每次被调用时重新计算平均值,此函数有权力调取收集器内的每一笔速度值。

    上述第一种做法(随时保持平均值)会使每一个SpeedDataCollection对象变大,因为你必须为用来存放目前平均值、累计总量、数据点数的每一个成员变量分配空间。然而averageSoFar却可因此而十分高效;它可以只是一个返回目前平均值的inline函数(见条款30)。相反地,“被询问才计算平均值”会使得averageSoFar执行较慢,但每一个SpeedDataCollection对象比较小。

    谁说得出哪一个比较好?在一部内存吃紧的机器上(例如一台嵌入式路边侦测装置),或是在一个并不常常需要平均值的应用程序中,“每次需要时才计算”或许是比较好的解法。但在一个频繁需要平均值的应用程序中,如果反应速度非常重要,内存不是重点,这时候“随时维持一个当下平均值”往往更好一些。重点是,由于通过成员函数来访问平均值(也就是封装了它),你得以替换不同的实现方式(以及其他你可能想到的东西),客户最多只需重新编译(如果遵循条款31所描述的技术,你甚至可以消除重新编译的不便性)。

    将成员变量隐藏在函数接口的背后,可以为“所有可能的实现”提供弹性。例如这可使得成员变量被读或被写时轻松通知其他对象、可以验证class的约束条件以及函数的前提和事后状态、可以在多线程环境中执行同步控制……等等。来自Delphi和C#阵营的C++程序员应该知道,这般能力等价于其他语言中的“properties”,尽管额外需要一组小括号。

    封装的重要性比你最初见到它时还重要。如果你对客户隐藏成员变量(也就是封装它们),你可以确保class的约束条件总是会获得维护,因为只有成员函数可以影响它们。犹有进者,你保留了日后变更实现的权利。如果你不隐藏它们,你很快会发现,即使拥有class原始码,任何改变public事物的能力还是极端受到束缚,因为那会破坏太多客户码。public意味着不封装,而几乎可以说,不封装意味着不可改变,特别是对被广泛使用的class而言。被广泛使用的class是最需要封装的一个族群,因为它们最能够从“改采用一个较佳实现版本”中获益。

    protected成员变量的论点十分类似。实际上它和public成员变量的论点相同,虽然或许最初看起来不是一回事。“语法一致性”和“细微划分之访问控制”等理由显然也适用于protected数据,就像对public一样适用。但封装呢?protected成员变量的封装性是不是高过public成员变量?答案令人惊讶:并非如此。

    条款23会告诉你,某些东西的封装性与“当其内容改变时所破坏的代码数量”成反比。所谓改变,也许是从class中移除它(或许这在某些方面有利,就像上述的averageSoFar)。

    假设我们有一个public成员变量,而我们最终取消了它。多少代码可能会被破坏呢?所有使用它的客户码都会被破坏,而那是一个不可知的大量。因此public成员变量完全没有封装性。假设我们有一个protected成员变量,而我们最终取消了它,有多少代码被破坏?所有使用它的derived class都会被破坏,那往往也是个不可知的大量。因此,protected成员变量就像public变量一样缺乏封装性,因为在这两种情况下,如果成员变量被改变,都会有不可预知的大量代码受到破坏。虽然这个结论有点违反直观,但经验丰富的程序库作者会告诉你,它是真的。一旦你将一个成员变量声明为public或protected而客户开始使用它,就很难改变那个成员变量所涉及的一切。太多代码需要重写、重新测试、重新编写文档、重新编译。从封装的角度观之,其实只有两种访问权限:private(提供封装)和其他(不提供封装)。

    请记住:
    1.切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。

    2.protected并不比public更具封装性。

  • 相关阅读:
    CefSharp.WinForms ChromiumWebBrowser 阻止打开新的窗口
    分享5款超实用的软件,小巧无广告
    【正点原子STM32连载】 第二十八章 硬件随机数实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
    Django vs Spring
    [NOIP2001 提高组] 一元三次方程求解
    计算机类毕业设计选题60套!太全了!快收藏!
    高通平台Android 蓝牙调试和配置手册-- Pairing Failure
    【Kali安全渗透测试实践教程】第6章 密码攻击
    Redis学习笔记——数据类型
    Web网上订购系统开题报告详解
  • 原文地址:https://blog.csdn.net/tus00000/article/details/136604807