C + + 中访问器方法(getter 和 setter)的约定

关于 C + + 中的访问器方法有几个问题已经在 SO 上提出,但是没有一个能够满足我对这个问题的好奇心。

我尽可能避免使用访问器,因为像 Stroustrup 和其他著名程序员一样,我认为一个类中有许多访问器是糟糕 OO 的标志。在 C + + 中,我可以在大多数情况下向类中添加更多的责任,或者使用 friend 关键字来避免它们。然而在某些情况下,您确实需要访问特定的类成员。

有几种可能性:

1. 根本不要使用访问器

我们可以将各个成员变量公开。这在 Java 中是不可行的,但是在 C + + 社区中似乎没有问题。但是,我有点担心的是,应该返回对象的显式副本或只读(常量)引用,这是否有些夸张?

2. 使用 Java 风格的 get/set 方法

我不确定它是否来自 Java,但我的意思是:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

3. 使用目标 C 风格的 get/set 方法

这有点奇怪,但显然越来越普遍:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

为了实现这一点,您必须为您的成员变量找到一个不同的名称。有些人添加下划线,有些人添加“ m _”。我也不喜欢。

你用什么款式? 为什么?

85369 次浏览
  1. I would not exclude accessors from use. May for some POD structures, but I consider them a good thing (some accessors might have additional logic, too).

  2. It doesn't realy matters the naming convention, if you are consistent in your code. If you are using several third party libraries, they might use different naming conventions anyway. So it is a matter of taste.

  1. I never use this style. Because it can limit the future of your class design and explicit geters or setters are just as efficient with a good compilers.

    Of course, in reality inline explicit getters or setters create just as much underlying dependency on the class implementation. THey just reduce semantic dependency. You still have to recompile everything if you change them.

  2. This is my default style when I use accessor methods.

  3. This style seems too 'clever' to me. I do use it on rare occasions, but only in cases where I really want the accessor to feel as much as possible like a variable.

I do think there is a case for simple bags of variables with possibly a constructor to make sure they're all initialized to something sane. When I do this, I simply make it a struct and leave it all public.

2) is the best IMO, because it makes your intentions clearest. set_amount(10) is more meaningful than amount(10), and as a nice side effect allows a member named amount.

Public variables is usually a bad idea, because there's no encapsulation. Suppose you need to update a cache or refresh a window when a variable is updated? Too bad if your variables are public. If you have a set method, you can add it there.

An additional possibility could be :

int& amount();

I'm not sure I would recommend it, but it has the advantage that the unusual notation can refrain users to modify data.

str.length() = 5; // Ok string is a very bad example :)

Sometimes it is maybe just the good choice to make:

image(point) = 255;

Another possibility again, use functional notation to modify the object.

edit::change_amount(obj, val)

This way dangerous/editing function can be pulled away in a separate namespace with it's own documentation. This one seems to come naturally with generic programming.

  1. That is a good style if we just want to represent pure data.

  2. I don't like it :) because get_/set_ is really unnecessary when we can overload them in C++.

  3. STL uses this style, such as std::streamString::str and std::ios_base::flags, except when it should be avoided! when? When method's name conflicts with other type's name, then get_/set_ style is used, such as std::string::get_allocator because of std::allocator.

In general, I feel that it is not a good idea to have too many getters and setters being used by too many entities in the system. It is just an indication of a bad design or wrong encapsulation.

Having said that, if such a design needs to be refactored, and the source code is available, I would prefer to use the Visitor Design pattern. The reason is:

a. It gives a class an opportunity to decide whom to allow access to its private state

b. It gives a class an opportunity to decide what access to allow to each of the entities who are interested in its private state

c. It clearly documents such exteral access via a clear class interface

Basic idea is:

a) Redesign if possible else,

b) Refactor such that

  1. All access to class state is via a well known individualistic interface

  2. It should be possible to configure some kind of do's and don'ts to each such interface, e.g. all access from external entity GOOD should be allowed, all access from external entity BAD should be disallowed, and external entity OK should be allowed to get but not set (for example)

From my perspective as sitting with 4 million lines of C++ code (and that's just one project) from a maintenance perspective I would say:

  • It's ok to not use getters/setters if members are immutable (i.e. const) or simple with no dependencies (like a point class with members X and Y).

  • If member is private only it's also ok to skip getters/setters. I also count members of internal pimpl-classes as private if the .cpp unit is smallish.

  • If member is public or protected (protected is just as bad as public) and non-const, non-simple or has dependencies then use getters/setters.

As a maintenance guy my main reason for wanting to have getters/setters is because then I have a place to put break points / logging / something else.

I prefer the style of alternative 2. as that's more searchable (a key component in writing maintainable code).

Let me tell you about one additional possiblity, which seems the most conscise.

Need to read & modify

Simply declare that variable public:

class Worker {
public:
int wage = 5000;
}


worker.wage = 8000;
cout << worker.wage << endl;

Need just to read

class Worker {
int _wage = 5000;
public:
inline int wage() {
return _wage;
}
}


worker.wage = 8000; // error !!
cout << worker.wage() << endl;

The downside of this approach is that you need to change all the calling code (add parentheses, that is) when you want to change the access pattern.

I've seen the idealization of classes instead of integral types to refer to meaningful data.

Something like this below is generally not making good use of C++ properties:

struct particle {
float mass;
float acceleration;
float velocity;
} p;

Why? Because the result of p.mass*p.acceleration is a float and not force as expected.

The definition of classes to designate a purpose (even if it's a value, like amount mentioned earlier) makes more sense, and allow us to do something like:

struct amount
{
int value;


amount() : value( 0 ) {}
amount( int value0 ) : value( value0 ) {}
operator int()& { return value; }
operator int()const& { return value; }
amount& operator = ( int const newvalue )
{
value = newvalue;
return *this;
}
};

You can access the value in amount implicitly by the operator int. Furthermore:

struct wage
{
amount balance;


operator amount()& { return balance; }
operator amount()const& { return balance; }
wage& operator = ( amount const&  newbalance )
{
balance = newbalance;
return *this;
}
};

Getter/Setter usage:

void wage_test()
{
wage worker;
(amount&)worker = 100; // if you like this, can remove = operator
worker = amount(105);  // an alternative if the first one is too weird
int value = (amount)worker; // getting amount is more clear
}

This is a different approach, doesn't mean it's good or bad, but different.

variation on #3, i'm told this could be 'fluent' style

class foo {
private: int bar;
private: int narf;
public: foo & bar(int);
public: int bar();
public: foo & narf(int);
public: int narf();
};


//multi set (get is as expected)
foo f; f.bar(2).narf(3);