您是否应该使用受保护的成员变量?


97

您是否应该使用受保护的成员变量?优点是什么?这会引起什么问题?

Answers:


73

您是否应该使用受保护的成员变量?

取决于您对隐藏状态的挑剔程度。

  • 如果您不希望任何内部状态泄漏,那么将所有成员变量声明为私有是可行的方法。
  • 如果您不太在乎子类可以访问内部状态,那么受保护就足够了。

如果开发人员出现并为您的类提供子类,则他们可能会弄乱它,因为他们无法完全理解。对于私有成员(除了公共接口),他们看不到具体的实现细节,这使您以后可以灵活地进行更改。


1
您可以通过get / set方法评论受保护变量与私有变量的性能吗?
杰克

3
我要说的是,您不必担心,除非您通过分析发现瓶颈最终成为访问者(几乎从来没有)。如果最终成为一个问题,可以采取一些技巧使JIT在事情上变得更聪明。例如,在Java中,您可以通过将访问器标记为final来暗示可以内联访问器。坦白地说,获取器和设置器的性能远不及处理系统组织和探查器确定的实际性能问题重要。
Allain Lalonde

23
@杰克:永远不要基于性能假设做出设计决策。您根据自己认为是最好的设计做出设计决策,并且只有当现实生活中的分析显示出设计中的瓶颈时,才可以进行修复。通常,如果设计合理,性能也将很好。
Mecki 2010年

使用私有成员(除了公共接口),他们看不到实现的具体细节,他们只能打开类并进行查找,所以这没有任何意义?!
黑色

2
@Black显然,Allain的意思是“他们无法访问 ”那些成员,因此无法针对他们构建代码,从而使类的作者可以自由地在以后删除/更改受保护的成员。(当然,使用pimpl惯用法可以使它们在视觉上隐藏,也可以从包括标题的翻译单元中隐藏出来。)
underscore_d

31

如今的普遍感觉是,它们在派生类和它们的基础之间引起了不适当的耦合。

与受保护的方法/属性相比,它们没有什么特别的优势(一次过,它们可能会有一点性能上的优势),并且它们还被用在一个非常深入的继承成为时尚的时代,而现在还不是。


2
no particular advantage over protected methods/properties应该no particular advantage over *private* methods/properties吗?
Penghe Geng 2014年

不,因为我正在/正在谈论派生类与其基之间进行通信的各种方式的优缺点-所有这些技术都将受到“保护”-区别在于它们是成员变量(字段)还是属性/方法(即某种子例程)。
迪安

1
感谢您的快速澄清。我很高兴在一个小时内收到原始海报的答复,以回答我对一个6岁的帖子的提问。您认为大多数其他在线论坛都不会发生这种情况:)
Penghe Geng

9
更引人注目的是,我实际上同意自己在这段时间里的看法……
迪恩

构造函数的一项工作是确保所有状态变量都已显式初始化。如果遵守此约定,则可以使用该super构造来调用父构造器;然后它将负责在父类中初始化私有状态变量。
ncmathsadist 2014年

31

通常,如果不是故意将某些内容视为公开的,则将其设为私有。

如果出现需要从派生类访问该私有变量或方法的情况,请将其从私有更改为受保护。

这种情况几乎从未发生过-我真的不喜欢继承,因为它不是建模大多数情况的特别好方法。无论如何,继续前进,无后顾之忧。

我想这对大多数开发人员来说都是很好的(也许是最好的方法)。

问题的简单事实是,如果其他开发人员在一年后才出现并决定需要访问您的私有成员变量,他们将只是编辑代码,将其更改为受保护的代码并继续开展业务。

唯一真正的例外是,如果您要以黑盒形式将二进制dll运送给第三方。它主要由Microsoft,那些“自定义DataGrid控件”供应商以及可能带有扩展库的其他一些大型应用组成。除非您属于该类别,否则不值得花费时间/精力来担心这种事情。


8

对我而言,关键问题是,一旦将变量设置为受保护的变量,就不能再允许类中的任何方法依赖其值在范围内,因为子类始终可以将其置于范围之外。

例如,如果我有一个定义可渲染对象的宽度和高度的类,并且将那些变量设置为受保护的,那么我就不能对(例如)宽高比进行任何假设。

至关重要的是,从代码作为库发布之时起,我就无法在任何时候做出这些假设,因为即使我更新了设置器以保持宽高比,我也无法保证变量是通过设置器设置的,还是可以通过现有代码中的获取器。

我班的任何子类也不能选择做出保证,因为它们也不能强制执行变量值,即使那是其子类的全部要点

举个例子:

  • 我有一个矩形类,其宽度和高度被存储为受保护的变量。
  • 一个明显的子类(在我的上下文中)是“ DisplayedRectangle”类,唯一的区别是我将宽度和高度限制为图形显示的有效值。
  • 但这现在是不可能的,因为我的DisplayedRectangle类无法真正约束这些值,因为它的任何子类都可以直接覆盖这些值,同时仍被视为DisplayedRectangle。

通过将变量约束为私有,我可以通过设置器或获取器强制执行我想要的行为。


7

通常,在极少数情况下,即使您完全控制使用它们的代码,我也会保留受保护的成员变量。如果您要创建公共API,那我就说永远不要。下面,我们将成员变量称为对象的“属性”。

在将成员变量设置为受保护成员而不是带有访问者的私有变量之后,您的超类无法执行以下操作:

  1. 读取属性时,会懒惰地动态创建一个值。如果添加受保护的getter方法,则可以延迟创建值并将其传递回。

  2. 知道何时修改或删除属性。当超类对变量的状态进行假设时,这可能会引入错误。为该变量创建一个受保护的setter方法可以保留该控件。

  3. 读取或写入变量时设置断点或添加调试输出。

  4. 重命名该成员变量,而不搜索可能使用它的所有代码。

总的来说,我认为这是极少数情况,建议您创建一个受保护的成员变量。您最好花几分钟的时间通过getter / setter公开属性,而不是花几个小时在修改保护变量的其他代码中查找错误。不仅如此,还可以确保您在不破坏相关代码的情况下添加将来的功能(例如延迟加载)。迟做比现在做起来难。


7

在设计级别上,使用受保护的属性可能是适当的,但是对于实现而言,将其映射到受保护的成员变量而不是访问器/更改器方法没有好处。

受保护的成员变量具有明显的缺点,因为它们有效地允许客户端代码(子类)访问基类的内部状态。这阻止了基类有效地保持其不变性。

出于同样的原因,受保护的成员变量也使编写安全的多线程代码变得更加困难,除非保证常量或将其限制在单个线程中。

访问器/更改器方法在维护下可提供更大的API稳定性和实现灵活性。

另外,如果您是OO纯粹主义者,则对象通过发送消息(而不是读取/设置状态)进行协作/通信。

作为回报,它们几乎没有优势。我不一定要将它们从其他人的代码中删除,但我自己不会使用它们。


4

在大多数情况下,使用protected是很危险的,因为您会在某种程度上破坏类的封装,而这种封装很可能会被设计不良的派生类破坏。

但我有一个很好的例子:假设您可以使用某种通用容器。它具有内部实现和内部访问器。但是您至少需要提供对其数据的3种公共访问权限:map,hash_map,类似矢量。然后,您将得到以下内容:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

我不到一个月前就使用了这种代码(因此代码来自内存)。经过一番思考,我相信尽管通用Base容器应该是一个抽象类,即使它可以很好地运行,因为直接使用Base会很痛苦,因此应禁止使用。

总结因此,您已保护派生类使用的数据。尽管如此,我们必须考虑到基类应该是抽象的事实。


它不会像公共成员那样破坏封装。可以说派生类可以使用未向类用户公开的类状态。
gbjbaanb

@gbjbaanb:您在矛盾自己“它不会像公共成员那样破坏封装了”,与“ [仅]派生类可以使用类的状态”不同。“受保护”是公共和私人之间的中间人。因此,“受保护的包装多少有些破损”仍然是正确的……
paercebal,

实际上,在C ++语言中,诸如std :: stack之类的容器适配器将使用称为“ c”的受保护变量来公开基础容器对象。
Johannes Schaub-litb

我知道这篇文章已经很老了,但是我觉得有必要加入。您不会“有点”破坏封装,而是完全破坏了封装。protected不比封装public。我愿意被证明是错误的。您要做的就是编写一个受保护成员的类,并禁止我对其进行修改。显然,该类必须是非最终的,因为使用protected的全部目的都是为了继承。某些东西被封装了,或者没有。没有中间状态。
Taekahn

3

简而言之,是的。

受保护的成员变量允许从任何子类以及同一包中的任何类访问变量。这可能非常有用,尤其是对于只读数据。我认为它们永远不是必需的,因为可以使用私有成员变量以及几个getter和setter来复制对受保护成员变量的任何使用。


1
相反,也不需要私有成员变量。只要使用public,就足够了。
爱丽丝

3

仅出于记录目的,在“脚注C ++”的第24项下,萨特在其中一个脚注中写道:“您永远不会编写一个具有公共或受保护的成员变量的类。 。)”


2

有关.Net访问修饰符的详细信息,请单击此处

受保护的成员变量没有实际的优点或缺点,这是您在特定情况下需要什么的问题。通常,将成员变量声明为私有变量并允许通过属性进行外部访问是公认的惯例。同样,某些工具(例如某些O / R映射器)期望对象数据由属性表示,并且无法识别公共或受保护的成员变量。但是,如果您知道希望子类(并且仅子类)访问某个变量,则没有理由不将其声明为受保护的变量。


希望子类访问变量与希望它们能够自由地对其进行变异非常不同。这是反对受保护变量的主要论据之一:现在,您的基类无法假定其任何不变量都成立,因为任何派生类都可以对受保护成员进行任何操作。这是反对他们的主要论点。如果他们只需要访问数据,那么...写一个访问器。:P(我确实使用了受保护的变量,尽管可能会使用过多的变量,并且我会尽量减少!)
underscore_d
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.