为什么std :: pair会暴露成员变量?

时间:2016-06-15 12:17:45

标签: c++ stl encapsulation

this开始,我们知道std::pair有两个成员变量,firstsecond

为什么STL设计者决定公开两个成员变量firstsecond,而不是提供getFirst()getSecond()

7 个答案:

答案 0 :(得分:94)

对于原始的C ++ 03 std::pair,访问成员的函数没有任何用处。

从C ++ 11及更高版本开始(我们现在处于C ++ 14,C ++ 17快速上升)std::pairstd::tuple的一个特例,其中std::tuple 1}}可以有任意数量的项目。因此,有一个参数化的getter是有意义的,因为发明和标准化任意数量的项名称是不切实际的。因此,您也可以将std::get用于std::pair

因此,设计的原因是历史性的,即当前std::pair是向更普遍性演变的最终结果。

在其他新闻中:

关于

  

据我所知,如果将两个成员变量封装在上面并提供getFirst();getSecond()

会更好
不,这是垃圾。

这就像说锤子总是更好,无论你是用钉子钉,用螺钉固定,还是修剪一块木头。特别是在最后一种情况下,锤子不是一种有用的工具。锤子可能非常有用,但这并不意味着它们总体上“更好”:那只是胡说八道。

答案 1 :(得分:30)

如果认为获取或设置值需要额外的逻辑(更改某些内部状态),则getter和setter通常很有用。然后可以将其轻松添加到方法中。在这种情况下,std::pair仅用于提供2个数据值。没有更多,没有更少。因此,添加getter和setter的详细程度将毫无意义。

答案 2 :(得分:13)

原因是不需要对数据结构施加真正的不变量,因为std::pair为两个元素建模通用容器。换句话说,类型std::pair<T, U>的对象被假定为分别对firstsecond类型的任何可能的TU元素有效。同样,其元素价值的后续突变也不能真正影响std::pair本身的有效性。

Alex Stepanov(STL的作者)explicitly presents这个一般设计原则在他的课程 Efficient Programming with Components 中,当评论singleton容器时(即,一个元素的容器)。

因此,尽管原则本身可以成为争论的源泉,但这也是std::pair形成背后的原因。

答案 3 :(得分:9)

如果认为抽象有必要将用户与设计选择隔离开来以及现在或将来对这些选择的更改,那么getter和setter非常有用。

“现在”的典型示例是setter / getter可能具有验证和/或计算值的逻辑 - 例如,使用setter作为电话号码,而不是直接暴露字段,以便您可以检查格式;对集合使用getter,以便getter可以向调用者提供成员值(集合)的只读视图。

“未来更改”的规范(though bad)示例为Point - 您应该公开xygetX()和{{ 1}?通常的答案是使用getter / setter,因为在将来的某个时候,您可能希望将内部表示从笛卡尔更改为极地,并且您不希望您的用户受到影响(或者他们依赖于那个设计决定。)

getY()的情况下 - 这个类的意图是现在并且永远直接表示两个且恰好两个值(任意类型),并根据需要提供它们的值。而已。这就是为什么设计使用直接成员访问,而不是通过getter / setter。

答案 4 :(得分:8)

可以说std::pair最好有访问器功能来访问其成员!特别是对于std::pair的退化案例,可能有一个优势。例如,当至少有一个类型是空的非final类时,对象可以更小(空基可以成为不需要获取自己地址的基础)。

在发明std::pair时,没有考虑这些特殊情况(我不确定当时的工作文件草案中是否允许空基础优化)。从语义角度来看,没有太多理由拥有访问器函数:显然,访问器需要返回非const对象的可变引用。因此,访问者不提供任何形式的封装。

另一方面,它使得优化器[稍微]难以看到当使用访问器功能时发生了什么,例如因为引入了额外的序列点。我可以想象孟李和亚历山大斯捷潘诺夫甚至测量是否存在差异(我也没有)。即使他们没有,直接提供对成员的访问肯定不比通过访问器功能慢,而反过来不一定是真的。

我不是决定的一部分,C ++标准没有理由,但我猜测这是一个故意决定让成员公开数据成员。

答案 5 :(得分:4)

getter和setter的主要目的是获得对访问权的控制权。也就是说,如果将“first”作为变量公开,那么任何类都可以读取和写入(如果不是const)而不告诉它是它的一部分。在许多情况下,这可能会造成严重问题。

例如,假设您有一个班级代表船上的乘客数量。您将乘客数量存储为整数。如果将该数字公开为裸变量,则外部函数可以写入该变量。这可能会让你遇到一个实际上有10名乘客的情况,但是有人将变量(可能是偶然的)改为50。这是乘客数量的一个吸气剂的情况(但不是一个定位器,它会呈现< em>同样的问题)。

getter和setter的一个示例是一个类,它表示要在其中缓存有关向量的某些信息的数学向量。假设你想存储长度。在这种情况下,更改vec.x可能会改变长度/幅度。因此,您不仅需要将x包装在getter中,还必须为x提供一个setter,它知道更新vector的缓存长度。 (当然,大多数实际的数学库都不会缓存这些值,从而暴露变量。)

因此,在使用它们的上下文中你应该问自己的问题是:这个类曾经是否可能需要控制或警告这个变量的变化?

像std :: pair这样的答案是平坦的“不”。没有理由控制对其成员的唯一目的是包含这些成员的成员的访问权限。当然,没有必要知道这些变量是否已被触及,考虑到这些变量只有两个成员,因此如果要改变,它就没有更新的状态。对不知道它实际包含的内容及其含义,因此跟踪它包含的内容是不值得的。

根据编译器及其配置方式,getter和setter可能会引入开销。在大多数情况下,这可能并不重要,但如果你把它们放在像std::pair这样的基础上,那将是一件非常重要的事情。因此,他们的加法需要合理 - 正如我刚才解释的那样,它不可能。

答案 6 :(得分:1)

我对于对面向对象设计没有基本了解的评论数量感到震惊(这是否证明c ++不是OO语言?)。是的,std :: pair的设计有一些历史特征,但这并不会使设计变得糟糕;也不应该以此为借口否认这一事实。在我发誓之前,让我回答一下评论中的一些问题:

  

你认为int还应该有一个setter和getter

是的,从设计的角度来看,我们应该使用访问器,因为这样做我们只会失去额外的灵活性。一些较新的算法可能希望将其他位打包到键/值中,如果没有访问器,则无法对其进行编码/解码。

  

如果getter中没有逻辑,为什么要在getter中包装东西?

你怎么知道getter / setter中没有逻辑?一个好的设计不应该限制基于猜测的实现的可能性。它应该提供尽可能多的灵活性。记住std:pair的设计也决定了迭代器的设计,并且要求用户直接访问成员变量,迭代器必须返回实际存储键/值的结构。结果证明这是一个很大的限制。有些算法需要将它们分开。有些算法根本不会明确存储键/值。现在他们必须在迭代期间复制数据。

  

与流行的看法相反,拥有除了使用getter和setter存储成员变量之外什么都不做的对象不是&#34;应该做的事情&#34;

另一种猜测。

好的,我会在这里停下来。

回答原始问题:std :: pair选择公开成员变量,因为设计它的人无法识别和/或优先考虑灵活合同的重要性。他们显然对于如何实现地图/散列表中的键值对有一个非常狭隘的想法/愿景,并且为了使情况变得更糟,他们让实施上的这种狭隘观点溢出顶部以破坏设计。例如,如果我想实现替换std:unordered_map,它基于具有线性探测的开放寻址方案在单独的数组中存储键和值?这可以极大地提高具有小键和大值的对的缓存性能,因为您不需要在值占用的空间上进行长跳以探测键。如果有std :: pair选择的访问器,为此编写一个STL样式的迭代器将是微不足道的。但现在,如果不引起额外的数据复制,就不可能实现这一目标。

我注意到他们还要求使用开放散列(即封闭链接)来实现std :: unordered_map。这不仅从设计的角度来看很奇怪(为什么你要限制事情的实现方式???),而且在实现方面也相当愚蠢 - 使用链表的链式哈希表可能是所有类别中最慢的。 go go go the web,我们可以很容易地发现std:unordered_map通常是哈希表基准测试的标准。它甚至比Java的HashMap慢(我不知道他们在这种情况下如何设法落后,因为HashMap也是一个链式哈希表)。一个旧的借口是,当load_factor接近1时,链式散列表往往表现更好,这完全无效,因为1)开放寻址系列中有很多技术可以解决这个问题 - 曾经听说过跳房子或知更鸟的哈希,以及后者实际上在那里度过了30多年的奇特岁月; 2)链式散列表为每个条目添加指针的开销(64位机器上的8个字节),所以当我们说unordered_map的load_factor接近1时,它不是100%的内存使用量!我们应该考虑到这一点,并将unordered_map的性能与具有相同内存使用量的替代方案进行比较。事实证明,像Google Dense HashMap这样的替代品比std :: unordered_map快3-4倍。

为什么这些是相关的?因为有趣的是,强制开放散列确实使得std :: pair的设计看起来不那么糟糕,因为我们不需要替代存储结构的灵活性。此外,std :: pair的存在使得几乎不可能采用更新/更好的算法来编写std :: unordered_map的插件替换。有时你想知道他们是否故意这样做,以便std :: pair的糟糕设计和std :: unordered_map的行人实现可以在一起存活更长时间。当然我在开玩笑,所以无论谁写这些,都不会被冒犯。事实上,使用Java或Python的人(好吧,我承认Python已经很长一段时间)会感谢你让他们感觉自己和C ++一样快#34;