[C++, Oop] Setters Vs. Friend Functions

Started by
5 comments, last by Katie 7 years, 8 months ago

Hey guys,

In a situation where one needs to design an object that has various things done to it, what are the pros and cons of using setter methods vs. external friend functions. For example, let's say you're making a game, and objects need to be drawn to the screen. What would the differences be in having the object draw itself with a draw () method in the object class versus having a drawObject () friend function that takes an object and as an argument and draws it?

Advertisement

Hey guys,

In a situation where one needs to design an object that has various things done to it, what are the pros and cons of using setter methods vs. external friend functions. For example, let's say you're making a game, and objects need to be drawn to the screen. What would the differences be in having the object draw itself with a draw () method in the object class versus having a drawObject () friend function that takes an object and as an argument and draws it?

I think you'll get better answers/interest with a more appropriate topic title.

Having objects draw themselves I feel is the simplest method and if your scope is small it's probably the better option. The down side of that is that you have render code sprawling all through your code and it becomes quite tightly coupled which is a problem for things like multi threading or targeting multiple graphical APIs. It also limits the amount of optimization you can do.

I'm sure there's plenty more but those few stand out to me.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

In C++, "friend" is a keyword you should never write.

It is something that you might be forced to write when you cannot come up with a better solution, and even then with a great big comment in the code saying "someone help fix this awful situation". I can't recall using the keyword in code for well over a decade, about 2002 or 2003; that was for a bunch of code that we didn't have the ability to modify for political reasons so we used language-lawyering to get it done.

You say you have a collection of actions you want to do. That is perfect.

Provide an interface to take those actions. Describe all the actions in the interface. Keep that interface abstract. Then create an implementation that implements the interface. Anywhere you code, rely on the abstract interface. Provide a factory method or similar to create and retrieve your concrete implementation.

If you have an object which is a bag-of-values, just make them public and write to them. Why bother with function authoring and then calling overhead if you're just going to put integers into integers?

There's nothing wrong with bag-of-value objects; just don't go around thinking that putting lots of boring boilerplate getter-setter methods in there somehow does anything magically OO for your design. All you're doing is writing a bunch of code which, if you're lucky, the compiler will just throw away for you.

If you have an object which is a bag-of-values, just make them public and write to them. Why bother with function authoring and then calling overhead if you're just going to put integers into integers?


As a general rule, if you're writing a class that has a bunch of setter/getter methods then you're probably doing it wrong.

But in the real world, you sometimes end up with "wrong" code.

Let's say you have the following scenario:

The code is failing somehow (crashing, doing the wrong thing, whatever). You break out the debugger and discover that foo.bar is 41 instead of 42! Something is changing that value.

Now you have two possible options: either you check everywhere in the code where foo.bar is assigned (and praying there isn't an alias to it somewhere you missed)

Or

You put a breakpoint in Foo::SetBar (possibly even conditional on bar != 42).

I know which one I'd rather deal with.

C# style auto properties are a really nice way of handling this.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

I both agree and disagree with frob here; he's dead wrong in that you should never use 'friend' but he's very right in his reasoning for why you shouldn't use it.

'friend' is a key part of C++ and - when used appropriately - is a very intentional part of good design.

The difference is this: if you are writing some code and decide that you need access to a class's private implementation and you then go and modify that class to add a friend, you are being naughty and should be punished.

If you are writing a class and decide that you need to expose a key operation of that class as a free-standing function that is otherwise intimately linked to that class and nothing else and so use a friend function declaration, you are writing C++ the way it's intended to be written.

Example:

class MyFoo {
  friend UnrelatedBar; // BAD BAD BAD: friend to an unrelated type
  int private_int = 0;
};
 
class UnrelatedBar {
  peekAtFoo(MyFoo& foo) { foo.private_int = ... } //  BAD BAD BAD: touching someone else's privates is totes inapprops
};

compared to:

class MyFoo {
  int private_int = 0;
 
  friend void coreFeatureFoo(MyFoo& foo); // JUST FINE: this is a free function that is an intentional part of the class
};
 
void coreFeatureFoo(MyFoo& foo) { foo.private_int = ... } // JUST FINE: touching your own privates is perfectly natural

Syntactically, there's no difference. It's all in the intent. As a very very rough guideline: if you're writing your friend function implementation in the same .h or .cpp file that you write your member function implementations, you're making correct use of friend. If you're writing the friend implementations in different files from the class itself, you're abusing the language is horrible ways, you are very naughty, and you need to be beaten with a copy of the ISO standard.

The advantage of a free function is that it is generically composable. The disadvantage is that it is not polymorphic. e.g., code relying on member functions and interfaces requires that your classes all implement that interface. If the class is external to your codebase, you either have to go modify the external code or you have to wrap it. With free functions, you can just add a new function that operates on that type (but note that since this is an external addition, _is is not a friend function_ - that's the whole point).

If you have a free function that needs to operate on a class you did not write then it should only operate using the public members of that class. Friend functions are only for free functions that are intimately part of the class's intended public members.

C++ relies heavily on this paradigm. Extension points are generally defined as free functions. Some of the default implementations of those extension points look for member functions, some don't/can't. If you use only the member function extension points you will be very limited in what can be extended (e.g., std::begin(x) is a free function because x.begin() can't be implemented where x is not a class type like C arrays; std::swap(x, y) is a free function because not every class has a swap(y) member function but every type that is copyable is swappable whether or not the swap member is present).

All that said, C++ programmers tend to write everything as member functions (even when the function has no need to be a member function) just to make our IDEs/tools easier to use (because they autocomplete and enable inspection of members, but generally aren't helpful with free functions). Proposals like N4474 were written partially to help address this issue, but sadly we're not getting anything like that until at least C++20 if not later/never.

Sean Middleditch – Game Systems Engineer – Join my team!

"You put a breakpoint in Foo::SetBar "

What's wrong with putting a watch on the foo.bar?

Most modern debuggers will quite happily let you say "break when value X is written to this".

Bag of values. If you want to watch for changes, watch the values.

This topic is closed to new replies.

Advertisement