【问题标题】:How to use getters and setters without generating a copy?如何在不生成副本的情况下使用 getter 和 setter?
【发布时间】:2019-08-15 19:24:33
【问题描述】:

我想知道如何对占用大量内存的成员变量使用 getter 和 setter。通常我会这样做:

class A
{
private:
  BigObject object;
public:
  BigObject getObject() const
  {
    return object;
  }

  void setObject(const BigObject& object)
  {
    this->object = object;
  }
};

但是,我相信这个 getter 和 setter 会复制我不想要的 BigObject。有没有更好的方法来做到这一点?

我想过这样做,但我在互联网上读到这不是一个好主意,因为如果使用不当会导致分段错误:

BigObject& getObject()
{
  return object
}

【问题讨论】:

  • 如果不制作副本,setter 将如何工作?
  • 你在互联网上哪里读到它会导致段错误?通过引用返回一个类成员通常是这样做的,所以这个建议非常令人惊讶。
  • getter 和 setter 打破封装,暴露对象的内部类型。我宁愿在类上实现一个动作方法,而不是让某人获得内部状态并将其放回去。
  • if used badly 任何事情都可能导致段错误。
  • 为什么使用(或修改或 'foos' 或 'bars' ...)A::object 数据的代码不在 A 中?该功能如何不是封装目标的一部分?请参阅有关“告诉,不要问”的文章。恕我直言,getter 和 setter 浪费了您(和我们)的时间。

标签: c++ getter-setter


【解决方案1】:

(如果您在这种情况下不关心封装,这意味着A::object 成员应该可以不受限制地被任何人修改,那么请查看SergeyA's answer)。

通过 const 引用返回以避免复制并仍然保持封装(意味着调用者不能错误地修改成员):

const BigObject& getObject() const
{
    return object;
}

如果调用者确实想要一份副本,他们可以自己轻松完成。

如果要在临时对象上使用 getter 时防止出现悬空引用(您提到的段错误),则只能在临时对象上实际调用 getter 时返回一个副本:

BigObject getObject() const &&
{
    return object;
}

const BigObject& getObject() const &
{
    return object;
}

这将在临时调用getObject() 时返回一个副本。或者,您可以通过删除特定的重载来完全防止临时调用 getter:

BigObject getObject() const && = delete;

const BigObject& getObject() const &
{
    return object;
}

请注意,这并不是一个有保证的安全网。它可以防止一些错误,但不是全部。函数的调用者仍应了解对象的生命周期。

顺便说一句,您还可以改进您的二传手。现在,无论调用者如何传递参数,它都会复制对象。您应该按值取而代之,并将其移动到成员中:

void setObject(BigObject object)
{
    this->object = std::move(object);
}

这要求BigObject 是可移动的。如果不是,那么这将比以前更糟。

【讨论】:

  • 请注意,尽管使用 rv ref 限定符重载 getter 是避免某些悬空引用情况的便捷方法,但它并不能阻止所有这些情况。该函数的用户仍然必须遵守生命周期规则。
  • 我不确定您对setObject 的定义如何比原来的有所改进。在原始定义中,参数是通过引用获取的,然后在将object 分配给this->object 时复制(尽管原始函数错误地使用object = object 而不是this->object = object)。在您的定义中,仍然有一个副本,因为该对象是按值传递的。
  • @jjramsey 这是一个改进。调用者可以移动构造参数,在这种情况下将不会有任何复制构造。但他们也可以在需要时复制构造参数。
  • @jjramsey 当取值然后移动时,这允许调用者将对象直接构造到设置器的参数中,从而完全避免复制:setObject(BigObject()); 如果设置器通过引用获取它,那么那个临时的总是会被不必要地复制。正如 eeronika 所提到的,这允许调用者将已经存在的对象移动到 AsetObject(std::move(big_obj)); 同样,不执行复制。
  • 不应该是BigObject getObject() const && { return std::move(object); }
【解决方案2】:

最佳解决方案:使您的类的代码完全符合:

struct A
{
  BigObject object;
};

说明 - 避免使用琐碎的 setter 和 getter。如果您发现自己将这些内容放入您的课程中,请直接公开该成员并完成它。

永远不要听别人说“但是如果将来我们添加非平凡的逻辑会怎样”?我看到的不只是健康剂量的微不足道的 setter 和 getter,它们已经存在了几十年,而且从未被非微不足道的东西取代。

【讨论】:

  • 请注意,这与返回引用所具有的缺乏封装完全相同的问题。但确实,当不需要封装时,这是最方便的方案。
  • 同意 SergeyA 关于琐碎的 getter 和 setter 的看法。毕竟这是 C++,而不是 java 或 C#!例如,当需要检查值的有效性和/或在过程中修改值时,应使用设置器。
  • 我将 Nikos C.comment 设置为答案,因为他的答案非常完整。但是我认为我会实施您的解决方案,因为我希望能够编辑 BigObject。我想知道我可以使用带有公共变量 BigObject 的类还是这很糟糕?
  • @ThatGuyAgain 我看不出有什么不好的。结构体有一些用例作为对象包,不需要封装,因此公共日期成员是最佳选择。
【解决方案3】:

通常的做法是:

class A
{
public:
  // this method is const
  const& BigObject getObject() const
  {
    return object;
  }

  // this method is not const
  void setObject(const BigObject& object)
  {
    object = object;
  }
private:
  BigObject object;
};

如果您需要获取只读对象 - 完全没问题。否则,请考虑架构更改。

另一种方法是存储 std::shared_ptr 并返回 std::shared_ptrstd::weak_ptr

【讨论】:

    【解决方案4】:

    您可以返回对它的引用,而不是返回成员的副本。这样就不需要复制成员了。

    我想过这样做,但我在互联网上读到这不是一个好主意,因为如果使用不当会导致分段错误

    解决办法就是不要乱用。

    返回对成员的引用是相当常见的模式,通常不被劝阻。虽然,对于快速复制的类型,当不需要引用成员本身时,返回一个副本通常更好。

    有一个解决方案可以避免复制和破坏封装:使用共享指针,并从 getter 返回该共享指针的副本。但是,这种方法具有运行时成本并且需要动态分配,因此它并不适合所有用例。


    在 setter 的情况下,你可以使用移动赋值来代替,这对于某些类型来说比复制赋值更有效。

    【讨论】:

      猜你喜欢
      • 2017-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-20
      • 2020-05-19
      • 1970-01-01
      相关资源
      最近更新 更多