7

我想知道 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_;
};
4

4 回答 4

11

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

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

假设后来你决定std::vector<Chest> chests在你的类中有一个私有字段。那么你就不想拥有一个int num_chests字段——它是非常多余的。你会想要拥有int num_chests() { return chests.size(); }.

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

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

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

回应评论说你应该只使用公共领域:

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

于 2013-04-26T20:25:35.567 回答
2

在几乎所有情况下,当您认为必须创建 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<value>.

于 2013-04-26T20:54:11.710 回答
2

您的接口的目的不是最简单的编程,而是最简单的使用扩展

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

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

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

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

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

于 2013-04-26T20:54:58.523 回答
0

我的目标是低耦合和高内聚;我避免使用 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.
}
于 2013-04-26T21:35:02.573 回答