Say I have a class with a private data member n
and a public get_n()
function.
When ov开发者_开发问答erloading the output operator for example, I can either use get_n()
or make it a friend and use n
.
Is there a 'best' choice? And if so, why?
Or is the difference going to be optimized away?
Thanks.
Use get_n, since this is not a proper usage of friend. And if get_n
is a simple return n
, the compiler is most likely going to inline it automatically.
I will answer your question with a question:
- Why did you create the public
get_n()
in the first place?
You've already gotten a lot of somewhat-conflicting answers, so what you undoubtedly need is one more that contradicts nearly all of them.
From an efficiency viewpoint, it's unlikely to make any difference. A function that just returns a value will undoubtedly be generated inline unless you specifically prohibit that from happening by turning off all optimization.
That leaves only a question of what's preferable from a design viewpoint. At least IMO, it's usually preferable to not have a get_n
in the first place. Once you remove that design problem, the question you asked just disappears: since there is no get_n
to start with, you can't write other code to depend upon it.
That does still leave a small question of how you should do things though. This (of course) leads to more questions. In particular, what sort of thing does n
represent? I realize you're probably giving a hypothetical example, but a good design (in this case) depends on knowing a little more about what n
is and how it's used, as well as the type of which n
is a member, and how it is intended to be used as well.
If n
is a member of a leaf class, from which you expect no derivation, then you should probably use a friend function that writes n
out directly:
class whatever {
int n;
friend std::ostream &operator<<(std::ostream &os, whatever const &w) {
return os << w.n;
}
};
Simple, straightforward, and effective.
If, however, n
is a member of something you expect to use (or be used) as a base class, then you usually want to use a "virtual virtual" function:
class whatever {
int n;
virtual std::ostream &write(std::ostream &os) {
return os << n;
}
friend std::ostream &operator<<(std::ostream &os, whatever const &w) {
return w.write(os);
}
};
Note, however, that this assumes you're interested in writing out an entire object, and it just happens that at least in the current implementation, that means writing out the value of n
.
As to why you should do things this way, there are a few simple principles I think should be followed:
- Either make something really private, or make it public. A private member with public
get_n
(and, as often as not, publicset_n
as well) may be required for JavaBeans (for one example) but is still a really bad idea, and shows a gross misunderstanding of object orientation or encapsulation, not to mention producing downright ugly code. - Tell, don't ask. A public
get_n
frequently means you end up with client code that does a read/modify/write cycle, with the object acting as dumb data container. It's generally preferable to convert that to a single operation in which the client code describes the desired result, and the object itself does the read/modify/write to achieve that result. - Minimize the interface. You should strive for each object to have the smallest interface possible without causing unduly pain for users. Eliminating a public function like
get_n
is nearly always a good thing in itself, independent of its being good for encapsulation.
Since others have commented about friend
functions, I'll add my two cents worth on that subject as well. It's fairly frequent to hear comments to the effect that "friend should be avoided because it breaks encapsulation."
I must vehemently disagree, and further believe that anybody who thinks that still has some work to do in learning to think like a programmer. A programmer must think in terms of abstractions, and then implement those abstractions as reasonably as possible in the real world.
If an object supports input and/or output, then the input and output are parts of that object's interface, and whatever implements that interface is part of the object. The only other possibility is that the type of object does not support input and/or output.
The point here is pretty simple: at least to support the normal conventions, C++ inserters and extractors must be written as free (non-member) functions. Despite this, insertion and extraction are just as much a part of the class' interface as any other operations, and (therefore) the inserter/extractor are just as much a part of the class (as an abstraction) as anything else is.
I'd note for the record that this is part of why I prefer to implement the friend functions involved inside the class, as I've shown them above. From a logical viewpoint, they're part of the class, so making them look like part of the class is a good thing.
I'll repeat one last time for emphasis: Giving them access to class internals can't possibly break encapsulation, because in reality they're parts of the class -- and C++'s strange requirement that they be implemented as free functions does not change that fact by one, single, solitary iota.
In this case the best practice is for the class to implement a toString
method, that the output operator uses to get a string representation. Since this is a member function, it can access all the data directly. It also has the added benefit that you can make this method vritual
, so that subclasses can override it, and you only need a single output operator for the base class.
Can the operator be implemented without using friend
? Yes- don't use friend
. No- make friend
.
精彩评论