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

时间:2009-04-10 11:56:34

标签: c# c++ properties

我最初来自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并使其只读/写(但对于面向公众的界面,这似乎并不好)。

缺点:

  • 需要很长时间才能输入,这个真的值得付出努力吗?一般来说。在某些情况下,优势使其值得付出努力,但我的意思是,就“良好实践”而言,是吗?

答案:

为什么选择the answer with less votes?我实际上非常接近选择veefu's answer;然而,我的个人意见(显然是有争议的),就是对布丁的回答。

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

肯定会比PrintBalance()更加灵活多变;如果我想以另一种方式向用户展示它而不是课程要求我怎么办?现在,在某种意义上,GetBalance()可能不足以证明“吸气者和制定者是好的”,因为它没有(或者,不应该)有一个伴随的制定者,并且在谈到其中,一个名为SetBalance(float f)的函数可能是坏的(在我看来),因为它意味着该函数的实现者必须在类的一侧操作该帐户,这不是一件好事。

14 个答案:

答案 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;
};

不要忘记getters and setters are somewhat evil.

答案 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());
}

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

  1. 整数是一种糟糕的货币数据类型
  2. 存款应该是帐户的功能
  3. 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类有一个   只读现在返回
    的属性   当前的日期和时间。每一次   你查询这个属性,它会
      返回不同的值。这是一个   错误,微软希望如此   他们可以通过制作来修复课程   现在是一种方法而不是属性。

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

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