获取和设置函数是否受C ++程序员的欢迎?

我原来是来自C#世界,我正在学习C ++。 我一直想知道在C ++中获取和设置函数。 在C#中,这些使用非常流行,而像Visual Studio这样的工具通过使它们变得非常容易和快速实现来促进使用。 但是,在C ++世界中似乎并非如此。

这是C#2.0代码:

public class Foo { private string bar; public string Bar { get { return bar; } set { bar = value; } } } 

或者,在C#3.0中:

 public class Foo { get; set; } 

可能人们会说,那是什么意思呢? 为什么不创建一个公共字段,然后在需要时将其设置为属性; 老实说,我其实不确定。 我只是出于良好的做法,因为我已经看过很多次了。

现在因为我已经习惯了这样做,我觉得我应该把习惯延续到我的C ++代码,但这真的有必要吗? 我没有像C#那样频繁地完成它。

无论如何,这是我收集的C ++:

 class Foo { public: std::string GetBar() const; // Thanks for the tip Earwicker. void SetBar(std::string bar); private: std::string bar; } const std::string Foo::GetBar() { return bar; } void Foo::SetBar(std::string bar) { // Also, I always wonder if using 'this->' is good practice. this->bar = bar; } 

现在,对我来说,这似乎是一大堆腿部工作; 考虑使用Visual Studio的工具,C#实现需要几秒钟的时间来实现,而C ++花了我更长的时间来打字 – 我觉得它不值得努力,特别是当替代品是5行长时:

 class Foo { public: std::string Bar; } 

从我收集到的,这些是优点:

  • 您可以更改get和set函数的实现细节,因此您可以返回更有趣的内容而不是返回私有字段。
  • 您可以稍后删除get / set并使其只读/写(但对于面向公众的界面,这似乎并不好)。

缺点:

  • 需要多年的打字,这真的值得努力吗? 一般来说。 在某些情况下,优势使其值得付出努力,但我的意思是,就“良好实践”而言,是吗?

回答:

为什么我选择的答案少了 ? 我实际上非常接近选择veefu的答案 ; 然而,我的个人意见(这显然是有争议的),是关于过度布丁的答案。

另一方面,我选择的答案似乎在争论双方; 我认为如果过度使用吸气剂和制定者邪恶的(我的意思是,当它没有必要时会打破商业模式),但为什么我们不应该有一个名为GetBalance()的函数?

当然,这将比PrintBalance()更通用; 如果我想以另一种方式向用户展示它而不是课程要求我怎么办? 现在,在某种意义上, GetBalance()可能不足以certificate“getter和setter是好的”,因为它没有(或者可能, 不应该 )有一个附带的setter,并且说到一个叫做SetBalance(float f)的函数SetBalance(float f)可能是坏的(在我看来),因为它意味着函数的实现者必须在类的一侧操作该帐户,这不是一件好事。

我认为在C ++中提供访问器比在C#中更重要。

C ++没有内置的属性支持。 在C#中,您可以将公共字段更改为属性,而无需更改用户代码。 在C ++中,这更难 。

为了减少输入,您可以实现简单的setter / getter作为内联方法:

 class Foo { public: const std::string& bar() const { return _bar; } void bar(const std::string& bar) { _bar = bar; } private: std::string _bar; }; 

不要忘记, 吸气剂和制定者有点邪恶。

冒着争论的风险,我会回到我在阅读“Holub on Patterns”时首次遇到的反对观点。 这是一个非常具有挑战性的观点,但在反思时对我有意义:

吸毒者和塞特犬是邪恶的

使用getter和setter与面向对象设计的基础相对立:数据抽象和封装。 过度使用getter和setter将使您的代码在较长时间内不那么敏捷和可维护。 它们最终公开了类的底层实现,将实现细节锁定到类的接口中。

想象一下,你的’std :: string Foo :: bar’字段需要从std :: string更改为另一个字符串类,比如说,更好地优化或支持不同的字符集。 您需要更改私有数据字段,getter,setter以及调用这些getter和setter的此类的所有客户端代码。

而不是将您的类设计为“提供数据”和“接收数据”,而是将它们设计为“执行操作”或“提供服务”。 问问自己为什么要编写“GetBar”function。 你在用这些数据做什么? 也许您正在显示数据或对其进行一些处理。 这个过程是否更好地暴露为Foo的方法?

这并不是说吸气剂和制定者没有他们的目的。 在C#中,我认为使用它们的根本原因是与Visual Studio GUI设计IDE接口,但如果你发现自己用C ++编写它们,最好退后一步,查看你的设计,看看是否有什么东西不见了。

我将尝试模拟一个例子来说明。

 // A class that represents a user's bank account class Account { private: int balance_; // in cents, lets say public: const int& GetBalance() { return balance_; } void SetBalance(int b) { balance_ = b; } }; class Deposit { private: int ammount_; public: const int& GetAmount() { return ammount_; } void SetAmmount(int a) { _balance = a; } }; void DoStuffWithAccount () { Account a; // print account balance int balance = a.GetBalance(); std::cout << balance; // deposit some money into account Deposit d(10000); a.SetBalance( a.GetBalance() + d.GetValue()); } 

不需要很长时间才能看到它的设计非常糟糕。

  1. 整数是一种糟糕的货币数据类型
  2. 存款应该是账户的一个function

getter和setter使解决问题变得更加困难,因为客户端代码DoStuffWithAccount现在绑定到我们用于实现帐户余额的数据类型。

所以,让我们传递一下这段代码,看看我们可以改进什么

 // A class that represents a user's bank account class Account { private: float balance_; public: void Deposit(float b) { balance_ += b; } void Withdraw(float w) { balance_ -= w; } void DisplayDeposit(std::ostream &o) { o << balance_; } }; void DoStuffWithAccount () { Account a; // print account balance a.DisplayBalance(std::cout); // deposit some money into account float depositAmt = 1000.00; a.Deposit(depositAmt); a.DisplayBalance(std::cout); } 

'浮动'是朝着正确方向迈出的一步。 当然,您可以将内部类型更改为“浮动”并仍然支持getter / setter惯用法:

 class Account { private: // int balance_; // old implementation float balance_; public: // support the old interface const int& GetBalance() { return (int) balance_; } void SetBalance(int b) { balance_ = b; } // provide a new interface for the float type const float& GetBalance() { return balance_; } // not legal! how to expose getter for float as well as int?? void SetBalance(float b) { balance_ = b; } }; 

但是不需要花很长时间才能意识到getter / setter安排会使你的工作量增加一倍并使问题变得复杂,因为你需要支持使用int的代码和使用浮点数的新代码。 存款function使扩展存款类型的范围更加容易。

类帐户类可能不是最好的例子,因为“获得”帐户余额是帐户的自然操作。 但总的来说,你必须小心搞定和吸气者。 不要养成为每个数据成员编写getter和setter的习惯。 如果你不小心,很容易暴露并锁定自己的实现。

在你的例子中:

 class Foo { public: const std::string GetBar(); // Should this be const, not sure? 

你可能是这个意思:

 std::string GetBar() const; 

const放在末尾意味着“此函数不会修改它被调用的Foo实例”,因此在某种程度上它将其标记为纯粹的getter。

纯粹的getter经常在C ++中出现。 std::ostringstream中的一个例子是str()函数。 标准库通常遵循对一对getter / setter函数使用相同函数名的模式 – str再次作为示例。

至于是否输入太多工作,是否值得 – 这似乎是一个奇怪的问题! 如果您需要让客户访问某些信息,请提供一个getter。 如果你不这样做,那就不要了。

[编辑]似乎我需要强调的是,setter需要validation参数并强制执行不变量,因此它们通常不像它们在这里那么简单。 [/编辑]


不是全部,因为额外打字。 我倾向于更频繁地使用它们,因为Visual Assist给了我“封装字段”。

如果你只在类声明中实现内联的默认setter / getter(我倾向于这样做 – 虽然更复杂的setter移动到正文),但是并不多。

一些说明:

constness 是的,getter应该是const。 但是,如果按值返回,则返回值为const是没有用的。 对于可能复杂的返回值,您可能希望使用const&尽管:

 std::string const & GetBar() const { return bar; } 

Setter链接:许多开发人员喜欢这样修改setter:

 Foo & SetBar(std::string const & bar) { this->bar = bar; return *this; } 

这允许调用多个setter:

 Foo foo; foo.SetBar("Hello").SetBaz("world!"); 

然而,这并不是普遍接受的好事。

__declspec(property) :Visual C ++提供此非标准扩展,以便调用者可以再次使用属性语法。 这有点增加了课堂上的练习,但使得调用者代码看起来更友好。


所以,总而言之,还有一些更多的工作,但是在C ++中做出了一些决定。 典型;)

对此没有严格的约定,就像在C#或Java中一样。 许多C ++程序员只是让变量公开,自己省事了。

正如其他答案所说,你不应该经常需要设置,并且在某种程度上,需要获得方法。

但是,如果你确实制造它们,就没有必要输入超过必要的东西了:

 class Foo { public: std::string Bar() const { return bar; } void Bar(const std::string& bar) { this->bar = bar; } private: std::string bar; }; 

在类中声明内联函数可以节省类型,并向编译器提示您希望内联函数。 而且它的输入并不比C#等价物多。 需要注意的一点是我删除了get / set前缀。 相反,我们只有两个Bar()重载。 这在C ++中相当常见(毕竟,如果它不接受任何参数,我们知道它是getter,如果它需要一个参数,它就是setter。我们不需要这个名字告诉我们),它节省了更多的打字。

我几乎没有在我自己的代码中使用getter和setter。 Veefu的回答对我来说很好。

如果你坚持使用吸气剂和/或定型器,你可以使用宏来减少锅炉板。

 #define GETTER(T,member) const T& Get##member() const { return member; } #define SETTER(T,member) void Set##member(const T & value) { member = value; } class Foo { public: GETTER(std::string, bar) SETTER(std::string, bar) private: std::string bar; } 

获取和设置数据成员数据成员: 错误
获取和设置抽象元素: 很好

银行示例中针对API设计的Get / Set参数是现货。 如果字段或属性允许用户违反业务规则,则不要公开它们。

但是,一旦您确定需要字段或属性,请始终使用属性。

c#中的自动属性非常易于使用,并且有许多场景(数据绑定,序列化等)不适用于字段,但需要属性。

如果您正在开发COM组件,那么是的,它非常受欢迎。

如果你必须用任何语言使用它们,那么获取和设定会给人们带来痛苦。

埃菲尔有很多更好的地方,所有不同的是你必须提供的信息量来得到答案 – 0参数的函数与访问成员变量相同,你可以在它们之间自由变化。

当您控制界面的两侧时,界面的定义似乎不是一个大问题。 但是,当您想要更改实现细节并且它会导致重新编译客户端代码时,就像C ++中的常见情况一样,您希望能够尽可能地减少这种情况。 因此, pImpl和get / set将在公共API中得到更多使用,以避免此类损坏。

如果在变量值中有约束,则Get和Set方法很有用。 例如,在许多数学模型中存在约束以将某个浮点变量保持在[0,1]范围内。 在这种情况下,Get和Set(特别设置)可以起到很好的作用:

 class Foo{ public: float bar() const { return _bar; } void bar(const float& new_bar) { _bar = ((new_bar <= 1) && (new_bar >= 0))?new_bar:_bar; } // Keeps inside [0,1] private: float _bar; // must be in range [0,1] }; 

此外,在阅读之前必须重新计算某些属性。 在这些情况下,重新计算每个cicle可能需要大量不必要的计算时间。 因此,优化它的方法是仅在阅读时重新计算。 为此,请重载Get方法,以便在读取变量之前更新变量。

否则,如果不需要validation输入值或更新输出值,那么将属性设为公开并不构成犯罪,您可以随意使用它。

如果您使用C ++ / CLI作为C ++的变体,那么它在语言中具有本机属性支持,因此您可以使用

 property String^ Name; 

这是一样的

 String Name{get;set;} 

在C#中。 如果您需要更精确地控制get / set方法,那么您可以使用

 property String^ Name { String^ get(); void set(String^ newName); } 

在标题和

 String^ ClassName::Name::get() { return m_name; } void ClassName::Name::set(String^ newName) { m_name = newName; } 

在.cpp文件中。 我不记得了,但我认为你可以对get和set方法(公共/私人等)拥有不同的访问权限。

科林

是的,get和set在c ++世界中很受欢迎。

如果你定义一个属性,编译器将发出set_和get_,所以它实际上只是保存了一些输入。

这是一个有趣的讨论。 这是我最喜欢的书“CLR via C#”。

这是我引用的内容。

就个人而言,我不喜欢属性,我希望它们不受Microsoftm.NET Framework及其编程语言的支持。 原因是因为属性看起来像字段,但它们是方法。 众所周知,这会产生惊人的混淆。 当程序员看到似乎正在访问字段的代码时,程序员做出的许多假设对于属性来说可能并非如此。 例如,

  • 财产可以是只读的或只写的; 现场访问始终是
    可读和可写。 如果你定义
    财产,最好同时提供
    获取并设置访问器方法。
  • 属性方法可能会抛出exception; 现场访问永远不会抛出
    例外。

  • 属性不能作为out或ref参数传递给方法; 一个领域可以。

  • 属性方法可能需要很长时间才能执行; 现场访问总是
    立即完成。 普通的
    使用属性的原因是
    执行线程同步,可以永久停止线程,并且
    因此,财产不应该
    如果是线程同步,则使用
    需要。 在那种情况下,一种方法是优选的。 此外,如果您的课程可以远程访问(例如,
    你的class级来自
    System.MashalByRefObject),调用
    属性方法会非常
    慢,因此,一种方法是
    优先考虑财产。 在我的
    意见,派生自
    MarshalByRefObject永远不应该使用
    属性。

  • 如果连续多次调用,则可能返回属性方法
    每次都有不同的价值; 一个
    field返回相同的值
    时间。 System.DateTime类具有返回的只读Now属性
    当前的日期和时间。 每次查询此属性时,它都会
    返回不同的值。 这是一个
    错误,微软希望如此
    他们可以通过制作来修复课程
    现在是一个方法而不是属性。

  • 属性方法可能会导致可观察到的副作用; 现场访问永远不会。 换句话说,类型的用户应该能够设置各种
    由任何类型定义的属性
    命令他或她选择没有
    注意到任何不同的行为
    类型。

  • 属性方法可能需要额外的内存或返回a
    引用不是的东西
    实际上是对象状态的一部分,因此修改返回的对象具有
    对原始物体没有影响;
    查询字段总是返回一个
    引用一个对象
    保证是原始对象状态的一部分。 使用a
    返回副本的属性可以是
    对开发人员来说非常困惑
    这个特征经常没有记录。