C ++中访问器方法(获取器和设置器)的约定


78

在SO上已经问了几个有关C ++中访问器方法的问题,但没有一个能满足我对此问题的好奇心。

我尝试尽可能避免访问器,因为像Stroustrup和其他著名的程序员一样,我认为带有很多此类的类是OO不好的标志。在C ++中,在大多数情况下,我可以为类增加更多责任,或使用friend关键字避免此类情况。但是在某些情况下,您确实需要访问特定的班级成员。

有几种可能性:

1.完全不使用访问器

我们可以仅公开各个成员变量。在Java中这是不行的,但对于C ++社区似乎可以。但是,我有点担心要返回一个对象的显式副本或只读(const)引用,是否夸大了?

2.使用Java风格的get / set方法

我不确定它是否完全来自Java,但是我的意思是:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

3.使用客观的C风格的获取/设置方法

这有点奇怪,但显然越来越普遍:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

为了使它起作用,您将不得不为您的成员变量找到一个不同的名称。有些人在下划线后面加上下划线,其他人则在前面加上“ m_”。我也不喜欢

您使用哪种风格,为什么?


5
除了小数据结构之外,什么给您的印象是公共成员变量在C ++中是可以的?通常也是不行的。
Georg Fritzsche 2010年

1
是的,这在C ++中也是不可行的。但是,如果您使用MFC,则可以假设它没问题。
vobject 2010年

最近被讨论为[C ++中的set / get方法](stackoverflow.com/questions/3632533),我通过serach “ c ++ getter setter”找到了它。另请参阅[Getter和Setter,指针或引用以及在C ++中使用的良好语法?](stackoverflow.com/questions/1596432),C ++ getters / setters编码样式以及其他可能的样式
dmckee ---前主持人小猫,2010年

4
请参阅本文(PDF),以了解为什么getters / setters完全是错误的
2010年

1
@vines:在实际的OO中,对象的接口(在类中以静态类型的语言定义)不应与对象的状态有关,而应与对象提供的操作有关。这些操作会更改对象的状态,这是一个实现细节。
sbi

Answers:


44

从维护角度来看,坐着400万行C ++代码(这只是一个项目),我的观点是:

  • 如果成员是不可变的(即const)或没有依赖性的简单成员(例如具有成员X和Y的点类),则不要使用getters / setter方法。

  • 如果private只有成员,也可以跳过getter / setter。我也算内部的成员PIMPL -班如private如果在.cpp单元短小。

  • 如果member是publicor或protectedprotected与一样糟糕public)并且non- const,non-simple或具有依赖项,则使用getters / setters。

作为一名维护人员,我想要使用吸气剂/塞特剂的主要原因是因为那时我有一个放置断点/记录/其他地方的地方。

我更喜欢替代方法2的样式,因为它更易于搜索(编写可维护代码的关键组成部分)。


2
接受针对社区Wiki问题的特定答案总是有些奇怪,因为每个答案都可以帮助您。但是,我认为您的理由是最好的。我目前正在尝试尽可能避免使用getter / setter方法。提出替代方案需要花费一些时间,但是它们往往更好。如果我需要您所提到的简单数据,则通常使用仅数据结构。
2010年

如果您必须调试访问器,那是不使用访问器的另一个原因。当然,还有更多有趣的地方可以设置断点。
hoodaticus

1
这是使它在调试方面很有用的一个好点,但是我确实同意可能有其他方法来获取该信息。否则我的想法是GETS&SETS永远不要一起使用。许多建议建议GET / SET允许您修改它的返回值,但是它不是GET / SET,它是一种执行一组操作的方法,应被命名为它对流程的全部意图。
杰里米·特里菲洛

8

2)是最好的IMO,因为它使您的意图最清晰。 set_amount(10)比更加有意义amount(10),并且作为一个很好的副作用,允许名为的成员amount

公共变量通常不是一个好主意,因为没有封装。假设您需要在更新变量时更新缓存或刷新窗口?如果您的变量是公共的,那就太糟糕了。如果有设置方法,则可以在其中添加它。


2
规范封装意味着隐藏逻辑单元的内部状态和行为细节。如果您确实通过公共接口访问私有数据成员,则说明您已经破坏了封装,无论您是使用访问器进行访问的。封装良好的类没有访问器:相反,它们具有活动的方法,这些方法以公共不可见的方式使用和影响内部状态。
ulidtko 2012年

1
关于“更改的需求”方案,例如,更改缓存或更新UI或在getId()呼叫内发送网络请求:通过将逻辑填充到getter / setter中,您将违反访问器方法的约定。您不会以这种方式中断客户端代码的编译,但是您肯定会中断其运行时操作,这甚至更糟。因为没有一个理智的人会假设您的getter有时可能会进行内存分配,或者您的setter可能会写入文件系统。在所有这些情况下,您都需要针对新要求进行重新设计
ulidtko 2012年

#1使您的意图最清晰,而不是#2。#2假装它通过将变量设为私有来封装内部状态,然后通过允许任何人(通过公共访问器)读取和写入该变量来取消隐藏状态的所有工作。#2不是意图很明确的案例,它只是一个空壳游戏。
hoodaticus

7
  1. 我从不使用这种风格。因为它可能会限制类设计的未来,并且显式的getter或setter对于好的编译器同样有效。

    当然,实际上,内联显式getter或setter会在类实现上创建尽可能多的基础依赖。它们只是减少了语义依赖性。如果更改它们,您仍然必须重新编译所有内容。

  2. 这是我使用访问器方法时的默认样式。

  3. 这种风格对我来说似乎太“聪明”了。我确实在极少数情况下使用它,但仅在我确实希望访问器尽可能感觉像变量的情况下使用。

我确实认为有一种简单的变量袋可能带有构造函数,以确保它们都被初始化为合理的情况。当我这样做时,我只是将其设为a,struct然后将其全部公开。


4
为“太聪明” +1。我建议不要滥用重载的可能性,它只会使读者更难,并且也很难搜索代码库。
Matthieu M.

-1用于“限制类设计的未来等”。您现在正在设计类。如果要稍后重新设计,请这样做。如果将来该领域消失了怎么办?访问器不会帮助您。如果保留它,返回一些垃圾值,则可能会发生您不知道的无声破坏。不要过度设计,否则您可以只对字段名称到值访问者进行映射即可完成。
einpoklum '16

您为什么要“访问器尽可能地感觉像一个变量”?它们不是变量,您为什么要混淆任何人以为它们是?
deetz

@deetz-好吧,@属性在Python中似乎很流行。这是将变量访问转换为函数调用的一种方法。
全方位

6
  1. 如果我们只想表示pure数据,那是一个很好的样式。

  2. 我不喜欢它:),因为get_/set_当我们可以在C ++中重载它们时,这确实是不必要的。

  3. STL使用这种样式,例如std::streamString::strstd::ios_base::flags,除非应避免使用!什么时候?当方法的名称与其他类型的名称冲突时,则使用get_/set_样式,例如std::string::get_allocator因为std::allocator


4
我担心STL在这方面可能会有点不一致。可悲的是,Stroustrup本人似乎并未表达对如何使用吸气剂/吸气剂的意见,只是他不喜欢其中许多人。
2010年

4

总的来说,我认为系统中有太多的实体使用太多的getter和setter并不是一个好主意。这仅表示设计不良或封装错误。

话虽如此,如果这样的设计需要重构,并且源代码可用,我宁愿使用“访客设计”模式。原因是:

一种。它给全班同学一个机会,让他们决定允许谁访问其私有国家

b。它给班级一个机会,以决定允许对其私有状态感兴趣的每个实体使用哪种访问权限

C。它通过清晰的类接口清楚地记录了此类外部访问

基本思想是:

a)如有可能,请重新设计,

b)重构为

  1. 所有对类状态的访问都通过一个众所周知的个人 界面

  2. 应该可以为每个这样的接口配置某种操作,例如 应该允许来自外部实体GOOD的所有访问,应该禁止来自外部实体BAD的所有访问,并且 应该允许外部实体OK进行访问,但是未设置(例如)


确实有趣的一点是,不知道为什么将其否决。但是我想如果某个特定的类需要访问特定的成员,我更喜欢使用friend关键字。
FHD

2
  1. 我不会排除访问器的使用。可能对于某些POD结构而言,但我认为它们是一件好事(某些访问器可能也具有其他逻辑)。

  2. 如果您的代码一致,则命名约定并不重要。如果您正在使用多个第三方库,则它们可能仍会使用不同的命名约定。因此,这是一个品味问题。


1

另一种可能是:

int& amount();

我不确定我是否会推荐它,但是它具有的优点是,这种不寻常的符号可以阻止用户修改数据。

str.length() = 5; // Ok string is a very bad example :)

有时,这也许只是一个不错的选择:

image(point) = 255;  

再次可能的是,使用功能符号来修改对象。

edit::change_amount(obj, val)

这样,危险/编辑功能可以通过其自己的文档放在单独的命名空间中。这似乎是通用编程自然而然的。


我没有对此表示反对,但是第一个答案是一个很差的答案,因为它公开了常规访问者隐藏的设计细节。另一个建议还不足以赎回答案。如果要尝试第一种样式,请至少返回某种类型的对象,该对象具有operator =对内部数据成员的引用,而不是对内部数据成员的引用。
2010年

为了清楚起见,我的回答仅列出了OP开始的可能性列表(也许我应该发表评论)。我不特别推荐那些可能性。就像Omnifarious所说的那样operator=,在第一种情况下使用代理类重载显然至少是一件好事。如果我想提供横向于类层次结构的编辑功能,以及在将这种功能与类明确分开的意义时,我会在某些情况下使用第二种功能。对于一个简单的二传手来说,这可能不是一个好主意。
log0 2010年

1

我已经看到了类的理想化,而不是整数类型来引用有意义的数据。

以下类似内容通常无法很好地利用C ++属性:

struct particle {
    float mass;
    float acceleration;
    float velocity;
} p;

为什么?因为p.mass * p.acceleration的结果是一个浮点数,而不是强制值。

定义类以指定目的(即使它是一个值,例如前面提到的金额)更有意义,并且允许我们执行以下操作:

struct amount
{
    int value;

    amount() : value( 0 ) {}
    amount( int value0 ) : value( value0 ) {}
    operator int()& { return value; }
    operator int()const& { return value; }
    amount& operator = ( int const newvalue )
    {
        value = newvalue;
        return *this;
    }
};

您可以通过运算符int隐式访问数量中的值。此外:

struct wage
{
    amount balance;

    operator amount()& { return balance; }
    operator amount()const& { return balance; }
    wage& operator = ( amount const&  newbalance )
    {
        balance = newbalance;
        return *this;
    }
};

Getter / Setter用法:

void wage_test()
{
    wage worker;
    (amount&)worker = 100; // if you like this, can remove = operator
    worker = amount(105);  // an alternative if the first one is too weird
    int value = (amount)worker; // getting amount is more clear
}

这是一种不同的方法,并不意味着它的好坏,而是不同的。


0

让我告诉您另外一种可能性,这似乎是最简洁的。

需要阅读和修改

只需将变量声明为public:

class Worker {
public:
    int wage = 5000;
}

worker.wage = 8000;
cout << worker.wage << endl;

只需要阅读

class Worker {
    int _wage = 5000;
public:
    inline int wage() {
        return _wage;
    }
}

worker.wage = 8000; // error !!
cout << worker.wage() << endl;

这种方法的缺点是,当您要更改访问模式时,需要更改所有调用代码(即添加括号)。


0

#3的变化,我被告知这可能是“流利的”风格

class foo {
  private: int bar;
  private: int narf;
  public: foo & bar(int);
  public: int bar();
  public: foo & narf(int);
  public: int narf();
};

//multi set (get is as expected)
foo f; f.bar(2).narf(3);

就功能而言,这是可以的,但在一定程度上超出了问题的范围。基本上,这是具有链集功能的c#风格的getter / setter。我听说过这种模式并不经常使用,我知道它肯定很容易替换。该问题还要求您解释一下,如果您愿意提供一种模式,为什么会在其他模式上使用这种模式。
Matias Chara
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.