CppCoreGuidelines C.9 尽量隐藏成员数据
02 August 2022
C.9: 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
返回的是正确的数值。想象一下,如果用户不知道 magnitude
和
unit
之间的关系,直接访问 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
数据混合在一起的地方