【问题标题】:Simple C++ getter/setters简单的 C++ getter/setter
【发布时间】:2013-08-09 02:08:26
【问题描述】:

最近我将我的 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)。更新:请参阅下面的 curacan 答案。

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

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

例子:

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 方法名称不同?
    • 将值作为参数传递? 只是为了验证输入还是有其他主要原因?

【问题讨论】:

  • 一个缺点是打字比公开数据成员要多,而且回报不多。
  • 我相信人们通常出于您所说的确切原因将值作为参数传递 - 输入验证。以及安全性,以及您能想到的任何其他用途。您可以确保对象具有更改某些值的“权限”,并进行验证以确保它们没有输入无效数据。
  • ...从语义上讲,您的 get-setter 与简单的公共成员之间没有区别。如果人们想要这种行为,他们只需创建一个公共成员,否则,我们使用更标准的 get set(value)。
  • 在生产代码中,使用value() = 3 的一个主要缺点是它需要更多的消化才能理解 value 作为 setter 被重载并返回可以修改的引用。当您使用现有 API 维护或编写新代码时,您希望可读性简单快速。 get()set(value) 明确且完全不言自明(即不需要 cmets)。
  • 这似乎是一种非常混乱的方式来做一些应该很简单的事情。如果您需要“验证输入”,因为您的成员可能彼此不同步(例如,日期结构,其中“递增”日期可能需要更改月份和/或年份),那么您可能应该提供这些操作(例如increment_day()),并且不能访问更改个别成员。如果单个成员不必同步,但需要在特定范围内,则最好将这些成员转换为类,并让他们自己进行验证。

标签: c++ class


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

【讨论】:

    【解决方案2】:

    这种方法不允许我:

    • 在 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)

    【讨论】:

    • 确实令人惊喜 :) 我开始按价值返回,但在此过程中改变了主意。现在正在尝试复制...
    • 请注意,它适用于int 和其他基本类型,但不要通过值返回更大的类,例如string 或其他。如果按值返回,会有一个你不想要的隐式副本。
    【解决方案3】:

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

    struct A {
      // getter
      int& getValue() const { return value_; }
    
      // setter
      void setValue(const int& value) {
        // validate value here
        value_ = value;
      }
    
      private:
        int value_;
    };
    

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

    【讨论】:

      猜你喜欢
      • 2011-06-27
      • 2010-11-04
      • 2011-11-09
      • 1970-01-01
      • 2013-05-24
      • 1970-01-01
      • 2016-08-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多