快捷搜索:

C++箴言:将数据成员声明为private

所有否决 public 数据成员的来由同样适用于 protected 数据成员。这就导出了数据成员应该是 private 的结论。

首先,我们将看看为什么数据成员不应该声明为 public.然后,我们将看到所有否决 public 数据成员的来由同样适用于 protected 数据成员。这就导出了数据成员应该是 private 的结论,至此,我们就停止了。

那么,public 数据成员,为什么不呢?

我们从先从语法同等性开始。假如数据成员不是 public 的,客户造访一个工具的独一措施便是经由过程成员函数。假如在 public 接口中的每件器械都是一个函数,客户就不必绞尽脑汁试图记着当他们要造访一个类的成员时是否必要应用圆括号。他们只要应用就可以了,由于每件器械都是一个函数。平生坚持这一方针,能节省很多挠头的光阴。

然则大概你不觉得同等性的来由是强制性的。应用函数可以让你加倍正确地节制成员的可存取性的事实又怎么样呢?假如你让一个数据成员为 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; // no access to this int

int readOnly; // read-only access to this int

int readWrite; // read-write access to this int

int writeOnly; // write-only access to this int

};

这种条分缕析的造访节制很紧张,由于多半数据成员必要被暗藏。每一个数据成员都必要一个 getter 和 setter 的环境是很罕有的。

还不信托吗?那么该拿出一门重炮了:封装。假如你经由过程一个函数实现对数据成员的造访,你可以在今后用一个谋略来调换这个数据成员,应用你的类的人不会有任何察觉。

例如,假设你为一个监视经由过程的汽车的速率的自动设备写一个利用法度榜样。每经由过程一辆汽车,它的速率就被谋略,而且那个值要加入到迄今为止网络到的所有速率数据的聚拢中:

class SpeedDataCollection {

...

public:

void addValue(int speed); // add a new data value

double averageSoFar() const; // return average speed

...

};

现在斟酌成员函数 averageSoFar 的实现:实现它的法子之一是在类顶用一个数据成员来实时变更迄今为止网络到的所有速率数据的匀称值。无论何时 averageSoFar 被调用,它只是返回那个数据成员的值。另一个不合的措施是在每次调用 averageSoFar 时从新谋略它的值,经由过程阐发聚拢中每一个数据值它能做成这些工作。

第一种措施(维持一个实时变更的值)使每一个 SpeedDataCollection 工具都对照大年夜,由于你必须为持有实时变更的匀称值,累计的和以及数据点的数量分配空间。可是,averageSoFar 能实现得异常高效,它仅仅是一个返回实时变更的匀称值的 inline 函数。反过来,无论何时被哀求都要谋略匀称值使得 averageSoFar 的运行对照慢,然则每一个 SpeedDataCollection 工具都对照小。

谁能说哪一个最好?在内存异常首要的机械(例如,一个嵌入式道旁设备)上,以及在一个很少必要匀称值的利用法度榜样中,每次都谋略匀称值可能是较好的办理规划。在一个频繁必要匀称值的利用法度榜样中,速率是基础的要求,而且内存不成问题,维持一个实时变更的匀称值更为可取。这里的重点在于经由过程经过一个成员函数造访匀称值(也便是说,经由过程将它封装),你能交换这两个不合的实现(也包括其他你可能想到的),对付客户,最多也便是必须从新编译。

将数据成员暗藏在功能性的接口之后能为各类实现供给弹性。例如,它可以在读或者写的时刻很简单地传递其他工具,可以查验类的不变量以及函数的前置或后置前提,可以在多线程情况中履行同步义务,等等。从类似 Delphi 和 C# 的说话来到 C++ 的法度榜样员会认同这种类似那些说话中的“属性”的等价物的功能,虽然必要附加一个带圆括号的额外的 set。

关于封装的要点可能比它最初显现出来的加倍紧张。假如你对你的客户暗藏你的数据成员(也便是说,封装它们),你就能确保类的不变量总能被保持,由于只有成员函数能影响它们。此外,你预留了今后改变你的实现决策的权力。假如你不暗藏这样的决策,你将很快发明,纵然你拥有一个类的源代码,你改变任何一个 public 的器械的能力也是异常有限的,由于有太多的客户代码将被破坏。public 意味着没有封装,而且险些可以说,没有封装意味着弗成改变,尤其是被广泛应用的类。然则仍旧被广泛应用的类大年夜多半都是必要封装的,由于它们可以从用一种更好的实现调换现有实现的能力中得到最多的益处。

否决 protected 数据成员的来由是类似的。实际上,它是一样的,虽然早先看起来彷佛不那么清楚。关于语法同等性和条分缕析的造访节制的论证就像用于 public 一样可以利用于 protected,然则关于封装又若何呢?难道 protected 数据成员不比 public 数据成员更具有封装性吗?实话实说,令人惊疑的谜底是它们不。

假如某物发生了变更,某物的封装与可能被破坏的代码数量成反比。于是,假如数据成员发生了变更(例如,假如它被从类中移除(可能是为了调换为谋略,就像在上面的 averageSoFar 中)),数据成员的封装性与可能被破坏的代码数量成反比。

假设我们有一个 public 数据成员,随后我们打消了它。有若干代码会被破坏呢?所有应用了它的客户代码,其数量平日大年夜得难以置信。从而 public 数据成员便是完全未封装的。然则,假设我们有一个 protected 数据成员,随后我们打消了它。现在有若干代码会被破坏呢?所有应用了它的派生类,范例环境下,代码的数量照样大年夜得难以置信。从而 protected 数据成员就像 public 数据成员一样没有封装,由于在这两种环境下,假如数据成员发生变更,被破坏的客户代码的数量都大年夜得难以置信。这并不相符直觉,然则富有履历的库实现者会奉告你,这是确切不移的。一旦你声明一个数据成员为 public 或 protected,而且客户开始应用它,就很难再改变与这个数据成员有关的任何工作。有太多的代码不得不被重写,重测试,重文档化,或重编译。从封装的不雅点来看,实际只有两个造访层次:private(供给了封装)与所有例外(没有供给封装)。

Things to Remember

·声明数据成员为 private。它为客户供给了造访数据的语法层上的同等,供给条分缕析的造访节制,容许不变量被强制,而且为类的作者供给了实现上的弹性。

·protected 并不比 public 的封装性强。

您可能还会对下面的文章感兴趣: