【问题标题】:Why do people write private-field getters returning a non-const reference?为什么人们要编写返回非常量引用的私有字段 getter?
【发布时间】:2017-07-24 01:07:30
【问题描述】:

我们都同意公共变量不利于封装等等。但是,我注意到很多代码都在做这种事情:

class foo {
private:
    int integer_;
    string someString_;
    // other variables
public:
    int& integer() { return integer_; }
    string& someString() { return someString_; }
    // other "functions"
}

int main() {
    foo f;
    f.integer() = 10;
    f.someString() = "something";
    return 0;
}

我已经看到很多地方都在使用它,但我不明白为什么。基本上,它返回对数据的引用,从而将其直接暴露给外部。所以封装并没有真正实现,不是从任何角度来看。

为什么常用这个?

【问题讨论】:

  • “为什么常用这个?”因为常见愚蠢。
  • 之所以常用,是因为很多程序员并不真正知道自己在做什么。
  • 所以他们基本上认为他们正在解决一个问题,而他们所做的只是在他们的代码中添加冗长的代码?
  • 正如其他评论者已经说过的那样,这很糟糕。 Look here
  • 不幸的是,常见做法和最佳做法并不总是相同的。顺便说一句,我相信这并不常见......

标签: c++ encapsulation getter-setter getter private-members


【解决方案1】:

我强烈反对将 non-const 引用返回到私有变量。不是因为它破坏了封装,而是因为它没有必要:为什么不首先创建变量public

打破封装不好,是的,但这并不意味着每个变量都应该是private。有些变量是要从用户那里读取和修改的,所以将它们设为public 是有意义的。比如std::pair,它有2个公共成员变量firstsecond。这不是坏习惯。

唯一没有意义的时候是不应该写入变量的时候。那会很糟糕,因为它实际上会破坏封装并使整个程序难以调试。

【讨论】:

  • std::pair 没有任何行为——如果你的类有行为,那么公共数据是致命的
  • @NeilButterworth 为什么?有什么区别?
  • 正如@NeilButterworth 所说,pair 没有任何行为。它只是数据的容器。所以这是最好的做事方式吗?拥有与演员不同的容器?除了你需要friend的情况
  • 好吧,如果你有一个有行为的类,比如std::string,如果你可以愉快地改变实现这个类的变量会发生什么?如果直接更改指向字符串存储的指针,std::string 会发生什么?
  • @NeilButterworth 指针不是用来修改的,所以它必须是private,是的。我从未说过它对每个变量都有意义,但有时确实如此。也许你有一个Window 类,其中widthheight 是公开的,可以修改和阅读。然后,下一次主循环读取宽度和高度,并相应地修改窗口
【解决方案2】:

我不得不部分不同意 @πάνταῥεῖ 和 @Rakete1111 的答案,考虑到一个类的定义是如何随着时间而演变的。

虽然这些 getter 方法通常是由刚刚听过“禁止暴露成员”口头禅的人编写的,但它们也可以有合法用途:

  1. getter 方法可能稍后被修改为在返回引用之前包含某种有效性检查或资源分配代码 - 直接访问数据成员是不允许的。虽然这意味着更改 class 的代码,但它不需要更改 class 用户 代码。此外,如果 getter 的实现没有在类头中公开,它甚至可能不需要重新编译类用户代码。 注意: 这样做可能是其他一些糟糕的设计选择的标志。
  2. getter 方法可以被子类覆盖(在这种情况下,它通常是一个虚拟方法),方式与上述类似。
  3. getter 方法稍后可能会将其返回类型替换为原始数据成员类型的proxy,而不是对实际成员的引用——它甚至可能不再存在。想想vector<bool> 是如何工作的;当您调用它的operator[] 时,您不会得到boolean&,您会得到某种代理,当分配或分配给它时,它会执行适当的位提取或设置。
  4. 非常量 getter 不适用于非常量实例。所以实际上它确实限制访问相对于直接暴露成员。 OP的示例类的作者是否真的打算这样做是一个不同的问题......

总结一下:“虚拟”非 const-reference getter 可以是其他有意义的代码的存根

话虽如此,让 getter 返回一个 const 引用或一个值通常是个好主意。或者只是在适当的情况下公开该字段(其中也有一些)。

【讨论】:

  • 您所说的确实有些道理。我确实意识到 C++ 没有 C# 的属性,这些属性实际上有助于轻松控制所有这些东西,正如您所说,无需更改用户代码。但同样,比较两种不同的语言并不是真正有效的
  • 听说过 YAGNI 吗?
  • @NeilButterworth: (1) 是的,但是 - 对于那些没有的人来说,必须要 link。 (2) 可能不需要它,但谁知道该代码的作者需要什么?
  • (3) 如果您正在设计继承层次结构,您可能已经需要它(或者至少 - 已经使用它)。 (4) 我自己从来没有真正写过这样的吸气剂:-)
  • @ein “getter 方法稍后可能会被修改以包含某种有效性检查” 正是这不可能暴露非 const 引用!
【解决方案3】:

我写过一次。我计划稍后返回并将字段 getter 替换为将堆栈对象返回到可以转换为原始类型并由原始类型分配的东西的东西。这使我可以稍后返回并拦截所有分配以进行验证。

有点压倒技术。该项目中的其他编码人员根本无法理解它。整个堆栈都被撕掉了。

【讨论】:

    【解决方案4】:

    此构造可用于调试目的。

    如果您有一个公共变量,您将无法轻松监控其使用情况。 将其转换为一对私有变量和方法返回引用将允许您放置断点和/或记录调用。

    但是,单独的 getter 和 setter 可以更好地达到相同的目的,因此这只是比普通公共变量的优势。

    【讨论】:

      【解决方案5】:

      有一个反复出现的口头禅,应该使用 getter/setter 函数来封装您的数据。因此,许多(没有经验或咖啡重载)程序员认为他们应该使用类似的东西:

      int& integer() { return integer_; }
      

      但这与简单的写作并没有太大区别:

      class foo {
      public: // <<<
          int integer_;
          string someString_;
          // ...
      };
      

      好吧,它添加了一个函数调用,但您无法控制客户端对引用的操作。


      如果你真的想提供一个 getter 函数,写:

      const int& integer() const { return integer_; }
      

      对应的setter函数如下:

      void integer(const int& value) {
          integer_ = value;
      }
      

      【讨论】:

      • 是的,它只是增加了不必要的代码的冗长性。然而很多人使用它。这让我大吃一惊:|
      • 是的,这就像来自std::vectorsize()
      • 你为什么不写:int integer() const { return integer_; }
      • @NeilButterworth 差异很小,我的示例可以很好地推断int 以外的其他类型(除了严格遵守 OP 的示例)。
      • @Everyone 输入更多,要阅读的代码更多,而且效率可能较低。如果您查看标准库中的类似函数,它们会返回整数值。
      猜你喜欢
      • 2018-01-26
      • 1970-01-01
      • 2011-05-06
      • 2020-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-06
      • 1970-01-01
      相关资源
      最近更新 更多