代码之家  ›  专栏  ›  技术社区  ›  Nick Bolton

Get和SET函数是否流行于C++程序员?

  •  30
  • Nick Bolton  · 技术社区  · 16 年前

    我最初是来自C的世界,我在学习C++。我一直想知道C++中的GET和SET函数。在C中,这些工具的使用非常流行,像Visual Studio这样的工具通过使它们非常容易和快速地实现来促进使用。然而,在C++世界中,情况似乎并非如此。

    这是C 2.0代码:

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

    或者,在C 3中:

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

    现在,对我来说,这似乎是一大堆小腿工作;考虑使用VisualStudio的工具,C语言实现将花费字面上的秒来实现,而C++花费了我更长的时间来进行类型化,我觉得它不值得付出努力,特别是当选择为5行长时:

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

    据我所知,这些优势是:

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

    缺点是:

    • 打字要花很长时间,是吗 真的? 值得付出努力吗?一般来说。在某些情况下,优势使它值得付出努力,但我的意思是,就“良好实践”而言,它值得吗?

    答:

    我为什么选择 the answer with less votes ?实际上我非常接近选择 veefu's answer 然而,我个人的观点(这显然是有争议的)是,答案太过刺激了布丁。

    另一方面,我选择的答案似乎是双方都在争论;我认为是能手和二传手。 如果过度使用(我的意思是,当它不必要并且会破坏业务模型时)是有害的,但是为什么我们不应该有一个称为 GetBalance() ?

    当然,这比 PrintBalance() ;如果我想以其他方式向用户展示它,而不是像班级希望的那样?现在,在某种意义上 GETBalOutlook() 可能没有足够的相关性来证明“能手和二传手都是好的”,因为它没有(或者可能, 不应该 )有一个伴随的setter,说到它,一个函数调用 SetBalance(float f) 可能是不好的(在我看来),因为这意味着函数的实现者必须在类之外操纵帐户,这不是一件好事。

    14 回复  |  直到 16 年前
        1
  •  31
  •   einpoklum    8 年前

    我认为,在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.

        2
  •  34
  •   veefu    16 年前

    冒着争论的风险,我将支持我在阅读“模式上的holub”时第一次遇到的一个相反的观点。这是一个非常具有挑战性的观点,但经过深思熟虑后对我来说是有意义的:

    能手和二传手都是邪恶的。

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

    假设您的“std::string foo::bar”字段需要从一个std::string更改为另一个字符串类,也就是说,它更好地优化或支持不同的字符集。您需要更改私有数据字段、getter、setter,以及这个类中调用这些getter和setter的所有客户机代码。

    与其将类设计为“提供数据”和“接收数据”,不如将它们设计为“执行操作”或“提供服务”。问问自己为什么要写一个“getbar”函数。你用这些数据做什么?也许你在上面显示数据或者在上面做一些处理。这个过程作为一种foo方法是否更好地暴露?

    这并不是说getter和setter没有他们的目的。在C i中,我相信它们使用的根本原因是与VisualStudio 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. 存款应该是账户的一个功能

    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);
    }
    

    “浮动”是朝正确方向迈出的一步。当然,您可以将内部类型更改为“float”,并且仍然支持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的安排会使您的工作负载翻倍,并且会使问题复杂化,因为您需要同时支持使用ints的代码和将使用float的新代码。deposit函数使扩展存储类型的范围变得更加容易。

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

        3
  •  11
  •   Daniel Earwicker    16 年前

    在您的示例中:

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

    你的意思可能是:

    std::string GetBar() const;
    

    const 最后的意思是“这个函数不修改它被调用的foo实例”,所以在某种程度上它将其标记为纯getter。

    C++中经常出现纯吸气剂。一个例子 std::ostringstream str() 功能。标准库通常遵循对getter/setter函数使用相同函数名的模式。- str 再次成为榜样。

    至于打字是否工作量太大,值得吗?这似乎是个奇怪的问题!如果需要让客户机访问某些信息,请提供getter。如果你没有,那就不要。

        4
  •  7
  •   peterchen    8 年前

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


    不是全部,因为要额外输入。现在视觉辅助给了我“封装字段”,我更倾向于使用它们。

    如果在类声明中只实现内嵌的默认setter/getter,那么legwork就不多了(我倾向于这样做——不过,更复杂的setter会移到主体中)。

    一些注释:

    常态: 是的,应该是警察。但是,如果按值返回,则将返回值设置为常量是没有用的。对于可能比较复杂的返回值,您可能希望使用const&though:

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

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

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

    它允许调用多个setter,例如:

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

    不过,这并不是一件被普遍接受的好事。

    __declspec(property) VisualC++提供了这种非标准扩展,以便调用方可以再次使用属性语法。这在类中增加了一点腿部工作,但使调用程序代码看起来更友好。


    所以,总之,有一点更多的法律工作,但是在C++中做出一些决策。典型的;

        5
  •  6
  •   Stack Overflow is garbage    16 年前

    在这方面没有严格的约定,比如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++中很常见(毕竟,如果它不带任何参数,我们知道它是吸气剂),如果它需要一个参数,它就是SETER。我们不需要这个名字来告诉我们),这样可以节省更多的输入时间。

        6
  •  4
  •   Community Mohan Dere    8 年前

    我很少在自己的代码中使用getter和setter。 Veefu's answer 我觉得不错。

    如果您坚持使用getter和/或setter,则可以使用宏来削减锅炉板。

    #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;
    }
    
        7
  •  3
  •   dmckee --- ex-moderator kitten    16 年前

    获取和设置数据成员qua数据成员: 坏的 .
    获取和设置抽象元素: .

        8
  •  3
  •   Jason Coyne    16 年前

    在银行业的例子中,反对使用API设计的get/set的论据是现场的。如果字段或属性允许用户违反业务规则,则不要公开它们。

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

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

        9
  •  1
  •   Otávio Décio    16 年前

    如果您正在开发COM组件,那么是的,它是 非常 流行的。

        10
  •  1
  •   Greg Domjan    16 年前

    如果你不得不用任何语言来表达,那么“获得”和“设定”都会给人带来痛苦。

    埃菲尔有它更好的地方,所有不同的是,你必须提供的信息量,以获得答案-一个函数与0参数是相同的访问一个成员变量,你可以自由地改变它们。

    当您控制接口的两边时,接口的定义看起来并不是一个大问题。然而,当你想改变实现细节,它造成重新编译客户端代码,这是常见的情况下,C++中,你希望能够尽量减少这一点。像这样的 pImpl 而get/set将在公共API中得到更多的使用,以避免这种损害。

        11
  •  1
  •   Amyr    10 年前

    如果变量值中有约束,则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方法,以便在读取变量之前对其进行更新。

    否则,如果不需要验证输入值或更新输出值,那么将属性公开并不是犯罪,您可以继续使用它。

        12
  •  0
  •   Colin Desmond    16 年前

    如果使用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方法(public/private等)拥有不同的访问权限。

    柯林

        13
  •  -1
  •   user88637    16 年前

    是的,get和SET在C++世界中很流行。

        14
  •  -3
  •   J.W.    16 年前

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

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

    这是我引用的。

    就个人而言,我不喜欢财产 我希望他们不是 在microsoftm.net中支持 框架及其编程 语言。原因是因为 属性看起来像字段,但它们 都是方法。这是众所周知的 导致大量的 混乱。当程序员看到 似乎正在访问的代码 字段,有许多假设 那是程序员做的 不适用于属性。为了 例子,

    • 属性可以是只读的或只写的;字段访问总是
      可读和可写。如果你定义
      物业,最好能同时提供
      获取和设置访问器方法。
    • 属性方法可能引发异常;字段访问从不引发
      一个例外。

    • 属性不能作为out或ref参数传递给 方法;字段可以。

    • 属性方法可能需要很长时间才能执行;字段访问始终
      立即完成。一个共同点
      使用属性的原因是
      执行线程同步,其中 可以永远停止线程,并且
      因此,属性不应
      如果螺纹同步
      必修的。在这种情况下,一种方法 优先考虑。另外,如果你的班可以的话 远程访问(例如,
      你的类是从
      system.masharbyrefobject),调用
      属性方法将非常
      因此,一种方法是
      优先于财产。在我的
      意见,类源于
      MarshalByRefObject不应使用
      性质。

    • 如果在一行中多次调用,则属性方法可能返回
      每次不同的值;a
      字段返回相同的值
      时间。System.DateTime类具有 返回的只读Now属性
      当前日期和时间。每一次 如果查询此属性,它将
      返回其他值。这是一个
      错误,微软希望
      他们可以通过制造
      现在是方法而不是属性。

    • 属性方法可能导致可观察到的副作用;字段访问 从来没有。换句话说,用户 一个类型应该能够设置
      属性由任何
      命令他或她选择没有
      注意到
      类型。

    • 属性方法可能需要额外的内存或返回
      指的不是
      实际上是物体状态的一部分, 所以修改返回的对象
      对原物无影响;
      查询字段始终返回
      对对象的引用
      保证是原件的一部分 对象的状态。与A一起工作
      返回副本的属性可以是
      对开发人员来说非常混乱,并且
      这一特点往往不是 文件化的。