哪种方法更适合实现get/set

Which method is better for implementing get/set?

本文关键字:实现 get set 方法      更新时间:2023-10-16

有两种实现get/set的方法。

方法1:

分别定义get和set

class my_class
{
  // ...
};
class main_class
{
public:
  my_class get_data() const
  {
    return m_data;
  }
  void set_data(my_class value)
  {
    m_data = value;
  }
private:
  my_class m_data;
};

注意:在这个方法中get是足够快的:http://cpp-next.com/archive/2009/08/want-speed-pass-by-value

另一个方法是(方法2):

定义两个get体,第一个是const,另一个是非const。

class my_class
{
  // ...
};
class main_class
{
public:
  const my_class& get_data() const
  {
    return m_data;
  }
  my_class& get_data() // Works like set.
  {
    return m_data;
  }
private:
  my_class m_data;
};

使用这些方法:

void main()
{
  main_class cls;
  // For method 1.
  my_class data;
  data = cls.get_data();
  cls.set_data(data);
  // For method 2.
  const my_class data1;
  my_class data2;
  data1 = cls.get_data();  // const get invoked.
  cls.get_data() = data2; // Like set beacuase non const get invoked.
}

我的问题是这些实现get/set的方法中哪个更好?

你知道更好的方法吗?


编辑:对于认为方法一更好的答案,在下列情况下你怎么说:

void main()
{
  main_class cls;
  // For method 1.
  cls.get_data().do_something_else(); // Not effictive for cls, because data losts.
  // For method 2.
  cls.get_data().do_something_else(); // Effictive for cls.    
}

对于任何自定义类,您应该始终使用引用来传递地址而不是值类。您还应该避免为编辑返回非const引用。请看下面我的建议。

class my_class
{
  // ...
};
class main_class
{
public:
  const my_class & get_data() const
  {
    return m_data;
  }
  void set_data(const my_class & data)
  {
    m_data = data;
  }
private:
  my_class m_data;
};

我知道这不会是c++纯粹主义者的普遍答案,在我学习Python和Ruby之前,我不会提出这种可能性…但是…既然你提供的getter和setter不做范围检查或特殊计算为什么不将成员设为public呢?

 class main_class
 {
  public:
    my_class my_data;
 }

当然,你将失去getter上的const,不能保证得到保护,但是你不能保证,因为你提供了一个set函数,它修改了成员。

第二个非常糟糕,因为它放弃了封装:您也可以将相应的字段设置为公共,任何人都可以在您的对象不知道的情况下访问它。您不能基于正在更改的数据执行范围检查或状态更新等

第二个将是一个非常糟糕的选择。使用setter的原因是能够控制用户如何修改成员变量。如果你只是给用户一个对你的成员的引用,你就失去了所有的控制权。

所以你基本上只剩下第一个方法了。下面是你可能喜欢也可能不喜欢的两个变体:

// First Variation
// ---------------
// In this one both the setter and the getter have the same name
// (which is the same as the member they control). To get a
// variable you do `int i = foo.var()` and to set it you do
// `foo.var(6)`. 
class Some
{
  public:
    int var() const {
        return var_;
    }
    void var(int v) {
        var_ = v;
    }
  private:
    int var_;
};
// Second Variation
// ----------------
// You can also make the setter return a reference to `this`.
// This allows you to chain setters, which can _sometimes_ be
// more readable but it also has a few disadvantages.
class Employee
{
  public:
    Employee& salary(double dollars) {
        salary_ = dollars;
        return *this;
    }
    Employee& name(const string& n) {
        name_ = n;
        return *this;
    }
  private:
    double salary_;
    std::string name_;
};
// You can now do this...
Employee emp;
emp.name("John Barlick").salary(500.00);
// ... But this can become quite ugly if you chain a large amount
// of setters (you'd then probably have to break the lines in
// order to keep the code readable). It also is (technically)
// less efficient. 
// In case you have lots of setters you could probably do this:
// emp.name("John Barlick")
//    .salary(500.00)
//    .some(787);
//    .another('g');  

通常定义getter/setter:

  const my_class& get_data() const
  {
    return m_data;
  }
  void set_data(const my_class& _data)
  {
    m_data = _data;
  }

首先,我认为这不是很有效

void set_data(my_class value)
{
  m_data = value;
}

你可能应该传递引用

void set_data(const my_class& value)
{
  m_data = value;
}

至于你应该选择哪个方法,这样想——在你的第二个方法中,你返回一个对内部对象的引用,用户可以完全自由地对它做任何事情。使用第一种方法,您可以控制用户可以或不可以做什么。

虽然像方法1这样的标准getter和setter可以提供"封装",但除非这些函数内联在头文件中,否则它们会增加很多开销。例如,在紧密循环中,即使您使用引用而不是按值传递(这需要昂贵的内存复制操作),为了在堆栈上设置getter/setter的激活记录以及函数的序言和epilogue,每次调用都必须不断地在x86中添加大约8条指令,这消耗了宝贵的CPU时间,并且确实损害了性能。因为getter和setter的作用不大,所以你真的不需要它们。

方法2实际上是许多STL容器所做的,就像std::vectoroperator[]一样,您重载相同的函数,但定义一个用于常量操作,另一个用于非常量操作…但是,当你可以公开访问数据成员时,你会增加不必要的开销(也就是说,它不像你是一些底层指针和其他内存管理的数据成员,比如STL容器)。如果你传递给它的函数需要一个常量引用,它无论如何都不会改变成员,所以真的没有必要创建这样的接口,除非你试图创建一个通用的接口来访问跨许多类的成员。如果你这样做,那么你应该查看一个纯虚基类来定义公共接口。

第二种方法看起来很尴尬。