5

最近我把我的getter和setter写成(注意:真正的类在getter/setter中做更多的事情):

struct A {
  const int& value() const { return value_; } // getter
        int& value()       { return value_; } // getter/setter
 private:
  int value_;
};

这允许我执行以下操作:

auto a = A{2}; // non-const object a

// create copies by "default" (value always returns a ref!):
int b = a.value();  // b = 2, is a copy of value :)
auto c = a.value(); // c = 2, is a copy of value :)

// create references explicitly:
auto& d = a.value();  // d is a ref to a.value_ :)
decltype(a.value()) e = a.value(); // e is a ref to a.value_ :)
a.value() = 3; // sets a.value_ = 3 :)

cout << b << " " << c << " " << d << " " << e << endl; // 2 2 3 3

const auto ca = A{1};
const auto& f = ca.value();  // f is a const ref to ca.value_ :)
auto& g = ca.value(); // no compiler error! :(
// g = 4; // compiler error :)
decltype(ca.value()) h = ca.value(); // h is a const ref to ca.value_ :)
//ca.value() = 2; // compiler error! :)

cout << f << " " << g << " " << h << endl; // 1 1 1

这种方法不允许我:

  • 验证设置器的输入(这是一个很大的 BUT),
  • 在 const 成员函数中按值返回(因为我希望编译器捕获对 const 对象的赋值:)ca.value() = 2。更新:请参阅下面的 cluracan 答案。

但是,我仍然经常使用它,因为

  • 大多数时候我不需要那个,
  • 这使我可以将类的实现细节与其接口分离,这正是我想要的。

例子:

struct A {
  const int& value(const std::size_t i) const { return values_[i]; }
        int& value(const std::size_t i)       { return values_[i]; }
  private:
   std::vector<int> values_; 
   // Storing the values in a vector/list/etc is an implementation detail.
   // - I can validate the index, but not the value :(
   // - I can change the type of values, without affecting clients :)
 }; 

现在的问题:

  • 这种方法还有其他我没有看到的缺点吗?
  • 人们为什么喜欢:
    • 具有不同名称的 getter/setter 方法?
    • 将值作为参数传递?只是为了验证输入还是有其他主要原因?
4

3 回答 3

6
  • 通常完全使用访问器/突变器是一种设计气味,即您的类公共接口不完整。通常来说,您需要一个有用的公共接口来提供有意义的功能,而不是简单地获取/设置(这比我们在 C 中使用结构和函数时要好一两个步骤)。每次你想写一个mutator,很多时候你想先写一个accessor,退一步问问自己"do I *really* need this?"
  • 只是习惯用语的人可能不准备期待这样的功能,因此它会增加维护人员了解您的代码的时间。
  • 同名方法与公共成员几乎相同:在这种情况下只需使用公共成员即可。当方法做两个不同的事情时,将它们命名为两个不同的事情。
  • 通过非常量引用返回的“mutator”将允许各种各样的别名问题,其中有人隐藏了成员的别名,依赖它以后存在。通过使用单独的 setter 函数,您可以防止人们对您的私人数据使用别名。
于 2013-08-07T16:51:15.710 回答
5

这种方法不允许我:

  • 在 const 成员函数中按值返回(因为我希望编译器捕获对 const 对象 ca.value() = 2 的赋值)。

我不明白你的意思。如果你的意思是我认为你的意思 - 你会感到惊喜:) 试着让 const 成员按值返回,看看你能不能做ca.value()=2......

但我的主要问题是,如果您想要某种输入验证,为什么不使用专用的 setter 和专用的 getter

struct A {
  int  value() const { return value_; } // getter
  void value(int v)  { value_=v; }      // setter
 private:
  int value_;
};

它甚至会减少打字量!(通过一个'=')当你设置。唯一的缺点是您不能通过引用修改它的函数来传递值。

关于编辑后的第二个示例,使用vector- 使用您的 getter/setter 比原始示例更有意义,因为您想要访问值(允许用户更改值)但不访问向量(您不'不希望用户能够改变向量的大小)。

因此,即使在第一个示例中,我确实建议将成员公开,但在第二个示例中,这显然不是一个选项,如果不需要输入验证,使用这种形式的 getter / setter 确实是一个不错的选择。

此外,当我有像你的第二种类型(带有向量)这样的类时,我喜欢访问beginend迭代器。这允许使用标准工具更灵活地使用数据(同时仍然不允许用户更改vector大小,并允许轻松更改容器类型)

另一个好处是随机访问迭代器有一个operator[](如指针),所以你可以做

vector<int>::iterator       A::value_begin()     {return values_.begin();}
vector<int>::const_iterator A::value_begin()const{return values_.begin();}
...
a.value_begin()[252]=3;
int b=a.value_begin()[4];
vector<int> c(a.value_begin(),a.value_end())

(虽然它可能丑到你还想要你的 getter/setter 除了这个)

于 2013-08-07T16:00:54.690 回答
0

关于输入验证:在您的示例中,分配发生在调用代码中。如果要验证用户输入,则需要将要验证的值传递到 struct 对象中。这意味着您需要使用成员函数(方法)。例如,

struct A {
  // getter
  int& getValue() const { return value_; }

  // setter
  void setValue(const int& value) {
    // validate value here
    value_ = value;
  }

  private:
    int value_;
};

顺便说一句,.NET 属性的实现是底层的方法。

于 2013-08-07T16:07:58.597 回答