02 August 2022

C++ 核心指南目录

“Minimize exposure of members”

理由

封装。信息隐藏。最小化意外访问。简化代码维护。

例子

template<typename T, typename U>
struct pair {
    T a;
    U b;
    // ...
};

因为 a 和 b 是公开的,不管注释部分提供了什么接口, pair 的用户都可以随意且独立地修改 a 和 b 的数据。在更大的代码库中,我们无法轻松找到哪些代码修改了 pair 的哪些成员数据。如果我们想强化成员变量的关系,最好是把它们设置成私有数据,然后通过构造函数和成员函数来强化他们之间的关系(不变体,invariant)。

在以下例子中,我们通过 meters 从成员变量中计算出以米为单位的距离数据。通过 set_unit 设置距离单位信息。这样,就不用直接访问成员数据进行计算,确保了 meters 返回的是正确的数值。想象一下,如果用户不知道 magnitudeunit 之间的关系,直接访问 magnitude 就可能用错数据值了。

class Distance {
public:
    // ...
    double meters() const { return magnitude*unit; }
    void set_unit(double u)
    {
            // ... check that u is a factor of 10 ...
            // ... change magnitude appropriately ...
            unit = u;
    }
    // ...
private:
    double magnitude;
    double unit;
    // 1 is meters, 1000 is kilometers,
    // 0.001 is millimeters, etc.
};

注意

如果哪些数据能被哪些用户直接访问还不确定,那么这个数据类型就很难修改改进。public 和 protected 成员数据就是这种情况。

例子

类通常会提供两类接口给用户。一类给继承此类的子类(protected),另一类提供给普通用户(public)。比如,继承的类可以跳过运行时检查,因为已经确保运行时正确了。

class Foo {
public:
    int bar(int x) { check(x); return do_bar(x); }
    // ...
protected:
    int do_bar(int x); // do some operation on the data
    // ...
private:
    // ... data ...
};

class Dir : public Foo {
    //...
    int mem(int x, int y)
    {
        /* ... do something ... */
        return do_bar(x + y);
        // OK: derived class can bypass check
    }
};

void user(Foo& x)
{
    int r1 = x.bar(1);      // OK, will check
    int r2 = x.do_bar(2);   // error: would bypass check
    // ...
}

注意

尽量不要用 protected 访问层级的数据

注意

按照以下顺序排列成员变量:public, protected, private

强化

  • 标记 protected 数据
  • 标记 public 和 private 数据混合在一起的地方