【问题标题】:Return by reference or create a typical setter/getter? [duplicate]通过引用返回或创建典型的 setter/getter? [复制]
【发布时间】:2013-04-26 20:11:19
【问题描述】:

我想知道 C++ 中的良好实践,我面临着为类成员制作 getter/setter 的问题。

那么,为什么不简单地通过引用返回成员,这样我就可以修改或访问它的值来读取它?具体来说,这是我的代码:

class Chest : public GameObject
{
public:
    Chest();
    ~Chest();

    static int& num_chests();

private:
    static int num_chests_;
};

这是一种不好的做法吗?我应该改用这些吗?

class Chest : public GameObject
{
public:
    Chest();
    ~Chest();

    static int num_chests();
    static void set_num_chests(int num_chests);

private:
    static int num_chests_;
};

【问题讨论】:

  • 如果 getter 没有做任何事情,而 setter 没有做任何事情,那么两者都是错误的。公开num_chests_。否则,需要第二个。通过引用返回成员的情况非常罕见。
  • 或者您可以使用选项 3 并将 num_chests 设为 public 数据成员,尤其是在您的 setter 不需要进行任何验证测试的情况下。
  • 我不认为这是特别糟糕的做法。使用/返回引用是 C++ 中的一个常见习惯用法。
  • 您当前是否有要为 num_chests_ 强制执行的约束或前提条件?如果是这样,请使用 setter 和 getter;否则将其设为公共成员。
  • @neodelphi - 编译器不强制执行良好的设计或样式。这给我们许多人带来了悲伤和就业机会。

标签: c++


【解决方案1】:

除非您强烈反对,否则请使用 getter 和 setter 成员函数。

int& num_chests() 或公共字段不好的原因是您将使用 num_chests 值的客户端代码与它实际上是一个字段(内部实现细节)这一事实相耦合。

假设稍后您决定在您的班级中有一个std::vector<Chest> chests 私有字段。那么你就不想有一个int num_chests 字段——这太多余了。你会想要int num_chests() { return chests.size(); }

如果您使用的是公共字段,现在您的所有客户端代码都需要使用此函数而不是之前的字段访问——num_chests 值的每次使用都需要更新,因为接口已更改。

如果您使用的是返回引用的函数,那么您现在会遇到问题,因为 chests.size() 是按值返回 - 您不能反过来按引用返回它。

始终封装您的数据。它只需要最少量的样板代码。

回应 cmets 说你应该只使用公共字段:

请记住,使用公共字段的唯一好处(除了一些微优化的远程可能性之外)是您不必编写样板代码。 “我的老师曾经讨厌我使用公共领域(而且他太烦人了)”对于使用公共领域来说是一个非常糟糕的论据。

【讨论】:

  • 你能说你是否认为pair类是一个糟糕的设计吗?
  • @Rayniery 否。那里的设计是这样的,如果我有一个 pair<T1, T2> pp.firstp.second 字段实际上与变量 p 一样具有变量语义。接口和语义他们是变量。 pair<T1, T2> 类型(以及 tuple)用作将变量(或值)捆绑在一起的轻量级方法 - 当然不是作为数据封装的工具!
【解决方案2】:

界面的目的不是编程最简单,而是使用扩展最简单强>。

如果你没有提供 setter 和 getter 方法,你就会为以后的麻烦做准备。例如:

  • 如果您需要在有人更改 num_chests 的值时发出通知,会发生什么?
  • 如果您需要验证而不是 num_chests 不能为负数,会发生什么情况?
  • 如果您需要在多线程环境中运行程序并且需要锁定读取直到写入准备好,会发生什么情况?

如您所见,对用户透明的界面也更易于防止用户错误,并且将来还会扩展;这一优势只需很少(如果有的话)额外成本。

另一方面,有时您确实希望返回指向内部成员的引用或指针。例如,标准库中的容器类通常提供一个data() 方法,该方法检索指向底层容器的指针(在const 和非const 变体中)。

因此,这不是一个硬性规则,但我会说返回对私有成员的非常量引用违背了 OO 编程的目的。

【讨论】:

    【解决方案3】:

    几乎在每种情况下,当您认为必须创建 setter 和 getter(即同时创建两者)时,您的设计都是错误的。

    想想num_chests 的目的是什么?你不能去任何不知道它是什么的地方。

    根据您的代码,我猜它包含关卡中的箱子数量。在这种情况下,您不想为每个人提供此值的设置器。您希望该值等于游戏中的箱子数量,并且通过在此处提供 setter,每个人都可以使该不变量无效。

    相反,您可以只提供 getter,并且可以控制它在您的类中的值。

    class Chest : public GameObject
    {
    public:
        Chest() { ++num_chests_; }
        ~Chest() { --num_chests_; }
    
        static int num_chests() { return num_chests_; }
    
    private:
        static int num_chests_;
    };
    
    int Chest::num_chests_ = 0;
    

    更多解释为什么我认为 getter 和 setter 是错误的决定。 如果你提供 setter 和 getter,你只会有控制变量的错觉。考虑std::complex。为什么是

    std::complex<double> my_complex(1.0, 5.0);
    my_complex.real(my_complex.real()+1);
    

    优于

    std::complex<double> my_complex(1.0, 5.0);
    my_complex.real()++;
    

    答案:在设计std::complex 时,C++ 中没有引用。此外,Java 没有 C++ 风格的引用,因此他们必须到处编写样板代码。现在,GCC 在此处返回非常量引用作为扩展,而 C++11 允许

    reinterpret_cast<double (&)[2]>(non_const_non_volatile_complex_variable)[0]
    

    reinterpret_cast<double (&)[2]>(non_const_non_volatile_complex_variable)[1]
    

    作为访问std::complex&lt;value&gt; 的实部和虚部的有效方式。

    【讨论】:

    • 我喜欢你的回答。你有动力去思考成员的目的,而不是说总是做这个或那个。我认为这是软件设计的主要问题之一:我们太习惯于告诉我们做一件事的“食谱”,而不是激励我们思考手头的设计和领域的特殊性。
    • "每次您认为必须创建 setter 和 getter(即同时创建两者)时,您的设计都是错误的。" - 说它是“错误的”可能有点太强烈了......这绝对是仔细检查你的设计的理由,但是有一个 getter 和 setter 是合法的。
    • @TimothyShields 编程当然没有灵丹妙药,但大多数时候都是设计失败。
    【解决方案4】:

    我的目标是低耦合和高内聚;我避免使用 getter 和 setter。

    如果你有 getter 和 setter,另一个对象必须知道如何调用它们。这就是耦合。

    尝试将您的对象解耦,但使它们具有凝聚力。我所说的凝聚力是指它们与系统的其他部分配合得很好。

    你的系统是什么?为什么你有 getter 和 setter?因为你要控制和显示这些对象。它们是模型,你有控制器和视图。

    很容易陷入控件/视图与模型之间存在耦合的陷阱。

    为避免耦合,让模型创建控件并更新视图。然后它就不必有任何 getter 或 setter。

    例如

    struct Instrumenter {
        virtual void addRange(int& value, int min, int max) = 0;
    };
    
    struct Renderer {
        virtual void render(std::string& description, int value) = 0;
    };
    
    struct GameObject {
        virtual void instrument(Instrumenter& instrumenter) = 0;
        virtual void display(Renderer& renderer) = 0;
    };
    
    struct Chest : GameObject {
        virtual void instrument(Instrumenter& instrumenter) {
            intrumenter.addRange(count, 0, 10);
        }
        virtual void display(Renderer& renderer) {
            renderer.render("Chest count", count);
        }
    private:
        int count;
    };
    

    那么你可以这样使用它:

    int main() {
        vector<shared_ptr<GameObject>> gameObjects;
        MyControlInstrumenter instrumenter;
        // ...
        for(auto& gameObject: gameObjects) {
            gameObject->instrument(instrumenter);
        }
        // etc.
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-05
      • 2014-03-19
      • 2018-11-28
      • 1970-01-01
      • 2019-03-31
      • 1970-01-01
      • 1970-01-01
      • 2017-09-26
      相关资源
      最近更新 更多