关于何时在私有成员变量上实现访问器而不是将它们公开的实践

Posted

技术标签:

【中文标题】关于何时在私有成员变量上实现访问器而不是将它们公开的实践【英文标题】:practices on when to implement accessors on private member variables rather than making them public 【发布时间】:2009-06-04 20:28:53 【问题描述】:

我知道公共成员变量和私有成员变量上的访问器之间的区别,并且已经看到一些关于堆栈溢出的帖子。我的问题更多地与实践有关。除了不破坏类不变量之外,通常在实用性方面的标准是使成员变量对访问器来说是公共的而不是私有的,反之亦然?提前感谢您的建议。

【问题讨论】:

【参考方案1】:

我的经验法则是:如果它不是一个非常简单的结构(例如 PointComplex),请将其作为私有成员变量的访问器来实现。如果你不确定你的结构是否足够简单,它可能不是;) ..

这样,您始终可以选择在不破坏现有代码的情况下扩展设置/获取变量的逻辑。是的,首先实现访问器可能需要做更多的工作,但如果之前是直接变量访问,那么将代码修改为访问器的工作要多得多。

【讨论】:

【参考方案2】:

使用访问器将强制客户端将成员视为函数,而不是原始内存。例如,它不允许获取所述成员的地址。因此,即使成员与简单 int 一样 POD,我仍然使用 get-set 函数对。从长远来看,这是值得的,因为重构可以改变实现,而不会出现“哦,等等,我给你的成员做空*”之类的惊喜。

性能方面,所有编译器都将内联此 set/get 访问器,并且程序集看起来就像引用公共成员字段一样。

我认为在过去的 5 年中,我从未编写过将其成员公开的课程。

【讨论】:

感谢您的建议。我已经有一些公开的结构,其中一些是指向大块已分配内存的指针。我想我可能需要做一些工作才能获得项目中的访问者。我最初没有将它们放在 b/c 中,我误以为它们可能会导致我的代码运行速度变慢并产生更多开销。 在一般情况下,开发人员希望在什么情况下将成员变量公开? 如果你公开“指向大块的指针”,事情可能会复杂得多。如果不知道更多细节,通常的解决方案是公开访问器以检索指针,而是公开服务(例如,“复制”数据的函数)或回调(例如,内部保留数据的“访问者”) 要点是一旦客户端代码知道你的内存布局,他们就会使用它并且你会坚持下去。更改任何东西都会破坏食物链上的代码,并慢慢变成维护的噩梦。嗯,连‘慢慢’都不算,想想…… 如果有可以检索指针的访问器,我是否会遇到程序可能变成噩梦来调试的情况?【参考方案3】:

使用公共成员变量的唯一时间是当类只是一个“比特包”时。这意味着您绝对没有机会必须添加不变量。在这种情况下,您可能应该使用struct

【讨论】:

【参考方案4】:

总是。永远不要公开成员变量。它没有任何优势:您始终可以使您的 get 和 set 方法内联。

【讨论】:

不得不不同意。我认为有一些例外——例如 3d Point 类。 “总是”和“从不”是危险的词。也就是说,在 99.9% 的情况下,成员变量应该是非公开的。 我宁愿说一个强大的 Always 并且只有 0.1% 需要它的人按需提供小字体;) 实际上,Point 类是我用来说明为什么应该始终使用访问器完成的示例之一。考虑一下,使用内联它是零成本,现在您可以防止更改极坐标,如果有人想使用第一象限坐标系代替第四象限,您就有了去处。 那么,std::pair 怎么样?你认为应该有访问器吗? 实际上,是的,我认为 std::pair 应该有访问器。我不断提醒自己,这不是 foo.first()。我们有 foo.first 和 bar.begin() 似乎不一致。【参考方案5】:

这是一个封装问题。您通常不希望向调用者公开成员变量,因为这样您就失去了对对象状态的重要控制。具有强大、简单接口且没有暴露字段的类更易于维护、调试和扩展。

【讨论】:

【参考方案6】:

我认为有两个重要的问题需要询问您的数据才能做出决定: 1) 数据是否受某些值范围的限制? 和 2)类是否依赖于从一个操作到另一个操作的数据状态的一致性? 如果您对这些问题中的任何一个回答“是”,那么您最好使用访问器和修改器。

例如,假设您有一个表示 2D 空间中的点的类。如果您可以将该点设置为任意位置,则没有理由将该数据设为私有。但是现在让我们说这些点必须在从 [-100,-100] 到 [100,100] 的图表上。在这种情况下,您可能希望限制可以分配该点的值(案例 1)。

另一个例子(这个有点做作):你有一个代表优先级队列的类,你将最小值存储在一个变量中。如果您更改该变量的值,那么它可能不再是最小值,在这种情况下您需要重新排序堆。由于堆的状态取决于该变量的值,因此您需要限制它的访问权限(但不允许修改它,或者在修改时根据需要对堆重新排序)。

【讨论】:

【参考方案7】:

如果客户端更改了该数据,或者您不希望客户端依赖于您的类实现,则当您的类的对象可能被搞砸时,将您的数据在您的类中设为私有。

意思是“几乎总是”。

【讨论】:

【参考方案8】:

虽然大多数答案都集中在设计/封装的角度(我同意普遍的共识,“为什么不使用 setter / getter?”)。我想补充一点,一旦你在一些大型遗留代码库中寻找一个错误,并且有公共成员,你永远永远不会编写一个没有 setter / getter 的类。

想象一下:您有一个庞大的代码库(超过 100 万行以上的代码),其中许多高度优化的遗留代码传递指针,进行位旋转等。

现在,经过几个小时的调试,您发现某个成员处于不一致状态,您想知道“嗯,这导致了错误,现在这个成员是如何获得该值的......”

1) setter/getters:你放一个断点,5分钟后,bug就解决了

2) 公众成员:您需要花费数小时进行烦人的侦探工作

公众成员:不做,没有收获,只有潜在的痛苦

【讨论】:

以上是关于关于何时在私有成员变量上实现访问器而不是将它们公开的实践的主要内容,如果未能解决你的问题,请参考以下文章

Getter Setter:使用还是不使用?

C++ - 使用 std::list,如何打印对象私有成员的链表?

何时以及为何在堆 C++ 上声明成员变量

子类可以继承父类的一切方法,成员变量,甚至是私有的,但是却不能够访问这些私有的成员变量和方法

在私有继承中公开构造函数

Swift 中的友元类(访问内部类的私有成员)