通过引用返回或创建一个典型的setter/getter

Return by reference or create a typical setter/getter?

本文关键字:一个 典型的 setter getter 引用 返回 创建      更新时间:2023-10-16

我想知道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成员函数。

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

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

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

如果你正在使用一个返回引用的函数,你现在有一个问题,因为chests.size()是一个按值返回——你不能反过来通过引用返回它。

总是封装你的数据。它只需要少量的样板代码。

对于你应该只使用公共字段的评论:

请记住,使用公共字段的唯一的好处(除了一些微优化的可能性)是您不必编写样板代码。"我的老师过去很讨厌我使用公共字段(他很烦人)"是一个非常糟糕的使用公共字段的理由。

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

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

  • 如果你需要在有人改变num_chests的值时发出通知,会发生什么?
  • 如果需要验证num_chests不能为负,会发生什么?
  • 如果您需要在多线程环境中运行程序,并且需要锁定读操作直到写操作准备就绪,会发生什么情况?

正如你所看到的,一个对用户透明的界面也更容易防止用户错误,并且在将来也可以扩展;这个优点只需要很少的额外成本。

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

所以,这不是一个硬性规则,但我要说的是,返回对私有成员的非const引用违背了OO编程的目的。

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

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

根据你的代码,我猜它包含关卡上的箱子数量。在这种情况下,您不希望为每个人都提供这个值的setter。你希望这个值等于游戏中箱子的数量,通过在这里提供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在这里返回非const引用作为扩展,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>的实部和虚部的有效方法

我的目标是低耦合和高内聚;我避免使用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.
}