2013-04-26 23 views
7

我想知道C++中的良好实践,而且我正在面临为类成员制作getter/setter的问题。通过引用返回或创建一个典型的setter/getter?

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

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_; 
}; 
+6

如果获取没有做任何事情,而二传手没有做任何事情,那么两者都是错误的。公开'num_chests_'。否则,第二个是必需的。通过引用返回成员非常罕见。 – 2013-04-26 20:13:26

+2

或者你可以使用选项3并将'num_chests'作为'public'数据成员,特别是如果你的setter不需要做任何验证测试。 – Praetorian 2013-04-26 20:13:30

+3

我认为这不是特别糟糕的做法。在C++中使用/返回引用是一种常见的习惯用法。 – 2013-04-26 20:13:40

回答

10

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

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

假设你以后决定在你的班级中有一个std::vector<Chest> chests私人领域。那么你不会想要有一个int num_chests字段 - 这是多余的。你会想要有int num_chests() { return chests.size(); }

如果您使用的是公共字段,现在您的所有客户端代码都需要使用此功能,而不是先前的字段访问 - 由于接口已更改,所以需要更新值的每个用法,因此需要更新num_chests值。

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

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

在回应评论说你应该只使用公共字段:

请记住,使用公共领域(比一些微优化的可能性极小其他)的唯一好处是,你不”不得不编写样板代码。 “当我使用公共领域时,我的老师常常讨厌(而且他太讨厌了)”是使用公共领域的非常糟糕的论据。

+0

你可以说,如果你认为配对班是一个糟糕的设计? – Rayniery 2013-04-26 20:47:28

+1

@Rayniery没有。有这样的设计,如果我有'pair p',那么'p.first'和'p.second'字段实际上具有变量语义,就像变量'p'一样。接口和语义*是*它们是变量。 '''类型(以及'tuple'也是)将变量(或值)捆绑在一起的轻量级手段 - 当然不是数据封装的工具! – 2013-04-26 20:55:15

2

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

更多关于为什么getters和setters在我看来是错误决定的解释。 如果你提供了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>实部和虚部的有效方法。

+0

我喜欢你的答案。你激励思考成员的目的,而不是说总是这样或那样做。我认为这是软件设计的主要问题之一:我们很习惯于“配方”,它告诉我们做一件事,而不是激励我们去思考设计和领域的特殊性。 – Rayniery 2013-04-26 21:03:56

+0

“每次你认为你必须创造二传手和吸气器(即同时),你的设计是错误的。” - 说它是“错误的”可能有点过于强烈......这绝对是重新检查你的设计的一个原因,但有一个吸气和安装者有合理的情况。 – 2013-04-26 21:04:28

+0

@TimothyShields当然,在编程中没有银弹,但大多数情况下,这是设计失败。 – milleniumbug 2013-04-26 21:08:26

2

产品界面的目的是是最简单的程序,但最简单的使用延长

如果您无法提供setter和getter方法,那么您正在设置自己以备后用。例如:

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

正如你所看到的,是透明的用户界面也更简单,以防止用户错误,也延长了未来;这个优点非常少(如果有的话)额外的成本。

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

所以,这不是一个硬性的规则,但我会说,返回非私人成员的非const引用打败了面向对象编程的目的。

0

我的目标是低耦合和高凝聚力;我避免了获得者和制定者。

如果你有getters和setters,另一个对象将不得不知道如何调用它们。这是耦合。

尝试去耦你的对象,但要使它们具有内聚性。我的意思是,它们与系统的其他部分配合良好。

你的系统是什么?你为什么有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. 
} 
相关问题