我最初来自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;
}
从我收集的内容来看,这些都是优点:
缺点:
为什么选择the answer with less votes?我实际上非常接近选择veefu's answer;然而,我的个人意见(显然是有争议的),就是对布丁的回答。
另一方面,我选择的答案似乎在争论双方;如果过度使用(我的意思是,当它没有必要并会打破商业模式时)我认为getter和setter 是邪恶的,但为什么我们不应该有一个名为GetBalance()
的函数?
肯定会比PrintBalance()
更加灵活多变;如果我想以另一种方式向用户展示它而不是课程要求我怎么办?现在,在某种意义上,GetBalance()
可能不足以证明“吸气者和制定者是好的”,因为它没有(或者,不应该)有一个伴随的制定者,并且在谈到其中,一个名为SetBalance(float f)
的函数可能是坏的(在我看来),因为它意味着该函数的实现者必须在类的一侧操作该帐户,这不是一件好事。
答案 0 :(得分:33)
我认为提供访问器在C ++中比在C#中更重要。
C ++没有内置的属性支持。在C#中,您可以将公共字段更改为属性,而无需更改用户代码。在C ++中,这是harder。
为了减少打字,你可以用内联方法实现琐碎的setter / getter:
class Foo
{
public:
const std::string& bar() const { return _bar; }
void bar(const std::string& bar) { _bar = bar; }
private:
std::string _bar;
};
答案 1 :(得分:33)
冒着争论的风险,我会回到我在阅读“Holub on Patterns”时遇到的反对观点。这是一个非常具有挑战性的观点,但在反思时对我有意义:
吸毒者和刽子手是邪恶的
使用getter和setter与面向对象设计的基础相对立:数据抽象和封装。从长远来看,过度使用getter和setter会降低代码的敏捷性和可维护性。它们最终暴露了类的底层实现,将实现细节锁定到类的接口中。
想象一下你的'std :: string Foo :: bar'字段需要从std :: string更改为另一个字符串类,比如说,它更好地优化或支持不同的字符集。您需要更改私有数据字段,getter,setter以及调用这些getter和setter的此类的所有客户端代码。
不是将您的类设计为“提供数据”和“接收数据”,而是将它们设计为“执行操作”或“提供服务”。问问自己为什么要编写“GetBar”功能。你在用这些数据做什么?也许您正在显示数据或对其进行一些处理。这个过程是否更好地暴露为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());
}
不需要很长时间才能看到设计非常糟糕。
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的代码和使用浮点数的新代码。存款功能使扩展存款类型的范围更加容易。
类似帐户的类可能不是最好的例子,因为“获取”帐户余额是帐户的自然操作。但总的来说,你必须小心吸气器和定位器。不要养成为每个数据成员编写getter和setter的习惯。如果你不小心的话,很容易将自己暴露并锁定在一个实现中。
答案 2 :(得分:11)
在你的例子中:
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。如果你不这样做,那就不要。
答案 3 :(得分:6)
[edit]我似乎需要强调,setter需要验证参数并强制执行不变量,因此它们通常不像在这里那么简单。 [/编辑]
不是全部,因为额外打字。我倾向于更频繁地使用它们,因为Visual Assist给了我“封装字段”。
如果你只是在类声明中实现内联的默认setter / getters(我倾向于这样做 - 更复杂的setter移动到正文),那么legwork就不多了。
一些注意事项:
<强>常量性:强> 是的,吸气剂应该是常数。但是,如果按值返回,则返回值为const是没有用的。对于可能复杂的返回值,您可能希望使用const&amp;虽然:
std::string const & GetBar() const { return bar; }
Setter chaining:许多开发人员喜欢这样修改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 ++中做出了一些决定。典型的;)
答案 4 :(得分:6)
对此没有严格的约定,就像在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。我们不需要这个名字告诉我们),它节省了更多的打字。
答案 5 :(得分:4)
我几乎没有在我自己的代码中使用getter和setter。 Veefu's answer对我来说很好看。
如果您坚持使用吸气剂和/或定型器,您可以使用宏来减少锅炉板。
#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;
}
答案 6 :(得分:3)
获取和设置数据成员数据成员:错误 获取和设置抽象元素:良好。
答案 7 :(得分:3)
银行示例中针对API设计的Get / Set参数是现货。如果字段或属性允许用户违反业务规则,则不要公开它们。
但是,一旦您确定需要字段或属性,请始终使用属性。
c#中的自动属性非常易于使用,并且有许多场景(数据绑定,序列化等)不适用于字段,但需要属性。
答案 8 :(得分:1)
如果您正在开发COM组件,那么是的,非常受欢迎。
答案 9 :(得分:1)
Eiffel有更好的地方,所有不同之处在于您必须提供的信息量才能得到答案 - 具有0个参数的函数与访问成员变量相同,您可以在它们之间自由更改。
当你控制界面的两面时,界面的定义似乎不是一个大问题。但是,当您想要更改实现细节并且它会导致重新编译客户端代码时,就像C ++中的常见情况一样,您希望能够尽可能地减少这种情况。因此,pImpl和get / set将在公共API中得到更多使用,以避免此类损害。
答案 10 :(得分:1)
如果变量值中存在约束,则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方法,以便在读取变量之前更新变量。
否则,如果不需要验证输入值或更新输出值,则将该属性设为公开并不构成犯罪,您可以随意使用。
答案 11 :(得分:0)
如果您使用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方法(公共/私人等)拥有不同的访问权限。
科林
答案 12 :(得分:-1)
是的,get和set在c ++世界很受欢迎。
答案 13 :(得分:-3)
如果你定义了一个属性,编译器会发出set_和get_,所以它只是保存了一些输入。
这是一个有趣的讨论。这是我最喜欢的书"CLR via C#".
这是我引用的内容。
就个人而言,我不喜欢属性 我希望他们不是 在Microsoftm.NET中受支持 框架及其编程 语言。原因是因为 属性看起来像字段,但他们 是方法。这已为人所知 造成惊人的数量 混乱。当程序员看到 似乎正在访问的代码 领域,有很多假设 程序员可以做到这一点 不属于财产。对于 例如,
- 属性可以是只读的或只写的;现场访问始终是 可读和可写。如果你定义了 一个财产,最好提供两个 获取并设置访问方法。
属性方法可能会抛出异常;现场访问永远不会抛出 例外。
属性不能作为out或ref参数传递给a 方法;一个字段可以。
属性方法可能需要很长时间才能执行;现场访问总是
立即完成。常见的一种 使用属性的原因是为了 执行线程同步,其中 可以永远停止线程,和 因此,财产不应该是 如果线程同步为
,则使用 需要。在那种情况下,一种方法 是优选的。此外,如果你的班级可以 远程访问(例如,
你的班级来自于 System.MashalByRefObject),调用
财产方法将非常多 慢,因此,方法是
喜欢的财产。在我的 意见,来自的分类 MarshalByRefObject绝不应该使用
属性。如果连续多次调用,属性方法可能会返回
每次都有不同的价值;一个
字段每个返回相同的值 时间。 System.DateTime类有一个 只读现在返回
的属性 当前的日期和时间。每一次 你查询这个属性,它会
返回不同的值。这是一个 错误,微软希望如此 他们可以通过制作来修复课程 现在是一种方法而不是属性。属性方法可能会导致可观察到的副作用;现场访问 从来不会。换句话说,是一个用户 一种类型应该能够设置各种
由任何类型的类型定义的属性 命令他或她选择没有 注意到任何不同的行为 类型。- 属性方法可能需要额外的内存或返回
引用不是
的东西 实际上是对象状态的一部分, 所以修改返回的对象有 对原始物体没有影响;
查询字段始终返回
引用一个对象 保证是原件的一部分 对象的状态。使用一个 返回副本的属性可以是
对开发人员来说非常混乱,而且 这个特征经常不是 记录。