那么公共变量有什么好处呢?

35

我是一个完全的新手,脑海中有很多问题,还有很多需要用C++来体验!有一件事情让我感到非常困惑,那就是公共变量的使用,我看到了很多像这样的代码:

class Foo {

private:
    int m_somePrivateVar;

public:
    void setThatPrivateVar (int const & new_val) {
        m_somePrivateVar = new_val;
    }

    int getThatPrivateVar (void) const {
        return m_somePrivateVar;
    }

};
为什么有人会将变量隐藏并实现访问器和修改器,当它们除了赋予新的值(没有范围检查等)或返回值之外,没有做任何其他事情呢?听说有一些原因在某些情况下很有说服力,但是想象一下,如果为一个有很多不需要检查等操作的变量的大型类以这种方式实现,那么会怎样呢? 那么我这样问你,你什么时候使用公共变量?你是否使用过?

22
目前来看,它们所做的不过是分配新值。重点在于接口Foo提供了什么,而不是Foo的当前实现。因此,在这个问题上,不要称某个函数为“SetThatPrivateVar”,因为公共接口不应该提及私有事项。 - Steve Jessop
1
@Steve:除非你在用Java编程,否则通过setter和getter暴露私有数据不仅受到鼓励,而且是必须的! - Kevin
3
我将加强但修改“甚至不要提”的部分——没有关于相关名称的严格规定,但这是因为名称反映了其目的。即使在将来,getter/setter名称不再用于获取/设置简单的成员变量并且原始成员变量不再存在,它们仍应该有意义。 - user180247
1
例如,Set_Mode 函数可能可以使用 m_Mode 成员变量,但即使没有 m_Mode 变量,函数名 Set_Mode 仍然是有意义的。比如,当您通过使用每个模式实现的类继承结构或者其他方式来切换到新的 PIMPL 实例时更改模式时,“Mode” 作为一个概念也是有意义的,与其是否命名为成员变量无关。 - user180247
1
我并没有为我上面写的这个例子考虑适当的命名,但是注意到了,我的下一个代码片段在stackoverflow上将遵循适当的原则,顺便说一句,谢谢大家! - Parsa Jamshidi
显示剩余2条评论
14个回答

41

通过隐藏变量并添加方法,类设计者可以在未来向这些方法中插入任意代码,而不会破坏直接使用属性的大量代码。

另外,请注意,提供大量的访问器/修改器方法通常是您的类设计需要重新审视以实现可能的改进的迹象。类方法应该实现真正的逻辑,而不仅仅是为每个成员提供访问。

我仅在struct形式中使用公共变量。例如,我可能有一个代表字符串->值映射的数据库表,其中值是复合数据结构。我只需编写一个结构,并使用例如std::map<std::string,MyStruct>来表示数据库表。我不需要对数据进行实际处理,只需要在需要时查找它并利用它。

正如几条评论中所指出的那样,即使struct也经常受益于谨慎使用方法,例如一些常见的构造函数来保持成员的合理初始化,以及清除函数以重用结构等。


1
有可能值得添加... 如果你的“对象”只是一组数据的组合,则结构体可以具有方法,但是将几个相关的计算方法或其他内容与其一起包括会更方便。当然,也会有一些人对具有成员的结构体提出抱怨。 - user180247
1
特别是在struct中,构造函数经常非常有用。对于第二段加1。 - Fred Foo
提供大量的访问器/修改器方法会得到加分。特别是像示例中的setThatPrivateVar这样的设置器方法只有在必要时才应该提供。避免它们自然会导致更多不可变类,这比可变类更容易理解。 - Raedwald
类方法应该实现实际逻辑,而不仅仅是提供对每个成员的访问。——有人应该告诉Java。 - sje397

24

在我看来,使用setter/getter最为重要的原因是隔离变化。例如,如果你需要添加范围检查,如果已经有一个setter,你可以轻松地在其中加入范围检查而不会对客户端代码造成影响。如果没有setter,那么所有客户端代码都需要更新以使用getter/setter,这可能是一场噩梦。


是的,这就是我所说的一些令人信服的理由,谢谢伙计! - Parsa Jamshidi

17

我在某种程度上同意其他回答中关于使用getter和setter方法以便于未来更改的观点,但仍然存在一个根本问题,那就是你将对象的内部数据暴露给其他对象进行修改。

最好的做法是通过方法请求对象对其持有的数据执行一些操作,而不是将对象视为值的集合。

这里有一篇有趣的文章,涉及到为什么getter和setter方法是有害的:Why Getter and Setter Methods are Evil,尽管它是针对Java编写的,但同样适用于C++。


5

任何公开的内容都成为对象契约的一部分。如果您公开数据,则在更改数据值时必须继续正常运行。对于结构类型对象,这可能非常适合。

具有大量getter和setter可能是一个警告信号,表明对象实际上是一个结构体,最好直接公开字段。但是,我已经实现了结构类型对象,以允许字段具有多个名称。根据需要,字段可以具有多个setter或getter,允许在不同域之间进行名称转换。真实字段具有描述性名称。额外的getter和setter使用特定于域的代码来获取或设置那些字段。

正如其他人所指出的,getter和setter表明存在字段。并不要求这样的字段存在,只要它的行为存在即可。将getter和setter设为public意味着公众有理由修改或读取该字段的值。三思而后行,您可以更改方法名称或不将其设置为public。


谢谢,从现在开始会更加认真地思考这类问题。 - Parsa Jamshidi

4

有一种观点认为:

如果与该类无关的代码直接依赖于特定成员,则更改该成员意味着更改访问它的每个代码片段。如果改为由成员函数访问,则可以保持接口的一部分不变,只需更改该类即可使外部代码正常工作。

使用“getter”和“setter”为对象之间的耦合提供了一定的灵活性。


3
也许举个例子会更有帮助。我们的一些类中有私有数据字段。我们为这些字段提供了getter和setter,它们仅仅是检索/设置未改变的值。然后通过分析发现,当这些字段实际被使用(在属于该类的方法中),大多数时候必须先将值转换为int,这会减慢计算速度。因此,我们把这些字段的类型改为int,在getter/setter中进行转换,但保持接口不变。如果这些字段是public的,则无法实现这一点。 - Axel
1
另一个说明getter/setter对于属性设置在GUI框架中有用的好例子是,setter允许我们在调用底层GUI API时保留类实例中值的本地副本。Delphi可视化组件库(VCL)是我遇到的第一个使用此getter/setter模型(通过属性读取和写入方法)的类库。在Object Pascal中,属性只不过是getter/setter语法糖而已。 - bit-twiddler

3

这些被称为“getter”和“setter”。使用它们的一个好处是,您可以在不更改接口的情况下随后添加额外的代码。在您的示例中,它们似乎没有太多作用,但在更复杂的类中,它们几乎是必不可少的。


3
好的,我理解您的问题是:为什么要将变量设为私有,然后再编写两个函数只是用来获取和设置值而没有进行任何检查?如果进行了检查,您会明白的,不是吗?例如,在设置时间类(Time class)的小时字段(hour field)时,检查hour <= 24是一个好主意。
但是当没有应用任何检查时,这个想法是:如果在某些时候您决定更改设置和获取函数,例如,在其中执行一些检查,则使用您的类的整个代码无需重新编译。
此外,封装的一般目的是通过其接口与类通信,而不知道它是如何实现的。您隐藏的越多,就越好。
何时使用公共变量?当您创建没有行为的对象时使用。那些只是数据的综合体。例如,请参见std::pair。一对只是一个结构体,具有公共first和second。
总的来说,不能给出使用哪种方式的严格标准,但是随着经验的积累,您会自己感觉到。
HTh

3
如Scott Meyers在《Effective C++》一书中所说:避免在公共接口中使用数据成员。为什么?因为这样更容易编写代码(所有内容都是成员函数),而且您可以更好地控制该变量的访问,以及Doug T.和其他人关于功能抽象的说法。
如果您不需要类(就像您的示例一样),则可以选择使用结构体...
如果您没有这本书,请查看Scott写的这篇文章 :)

1
我不同意公共成员是邪恶的这种观点。C++并不是C#。在使用C++编程时,我会避免使用我不需要的东西。如果:
  • 你的公共变量不能被设置为未定义、越界或某种方式损坏的值
  • 你知道没有人会试图破坏你的对象(这意味着使用它的模块完全知道该怎么做)
  • 你不需要任何其他由get/set操作引起的检查或对象修改
  • 你百分之百确定将来不需要上述任何一项
使用公共成员完全没有问题。
尽管有些人说,访问公共变量比使用一些函数调用更容易编写出干净的代码。当然,这取决于项目的类型以及类将如何使用以及将来可能如何使用。为了预防而编写getter和setter在我看来是毫无意义的。 作为参考,请阅读C++指南(C.131)

0

我知道在Java中我们使用公共变量,事实上,

public static final

变量作为指定常量的手段,这是在枚举引入之前的做法。

例如,

class Direction
{

public static final String NORTH = "north";
...
}

我有一段时间没研究C++了,所以不确定是否有枚举类型可用。如果C++中没有枚举,你可以使用上面类似的代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接