Stuck in between
On one side, there is the encapsulation mantra, which seems to be quite popular, like in Scott Meyers' books, multiple advises ... even clang-tidy has a warnings as follow:
member variable 'memberA' has public visibility
clang-tidy(cppcoreguidelines-non-private-member-variables-in-classes)
And on the other side, there is C++ pass-by/return-by value and their ghosts of performance, early optimization ...
I will not argue here about any of those points, just discuss how I made my way through those two concepts that seemed to be, at first, quite opposed.
Struct or classes with public data members
"A good old struct". With no real OOP mindset, it is sometime my favorite first approach, as soon as I play with everything which is not a POD (Plain Old Data) object, easily copyable.
Discussing recently with a colleague, I think I finally understood when to use structs, he said something like:
We use them when we group together objects that do not depend on each others
But in any other cases ? Encapsulation to the rescue.
Encapsulation
So OK, I have potentially complex objects (not PODs and certainly not easily copyable), and I make them private (farewell, clang-tidy warning). And what now ? How do I retrieve and modify them ?
Getters to the rescue, or ... is it ?
A getter 'by the book'
So with my actual state of knowledge I should:
- not return by value to avoid a copy, so return by reference
- return a const reference to avoid modifications
- inline for optimization
- noexcept for optimization (whaaat ? This I still have to read about ...)
I then create a getter which looks like:
const MyComplicatedType& getMemberA() noexcept {
return memberA_;
}
Not bad. I am quite proud to be honest.
But ...
Wait, if I return a reference, how could I be sure my member could not be modified whatsoever ? Would C++ be more magical than any other languages on this matter ?
Safety is a const_cast away
It is not a so advanced C++ trick to use const_cast to remove the constness, so if I do something like this:
auto& constMemberA = myobject.getMemberA();
auto& memberA = const_cast<MyComplicatedType&>(constMemberA);
memberA = whateverIwantOfTypeMyComplicatedType; // game over
It works. I can modify my so preciously encapsulated member.
Anyway, there are still advantages over a public member access and no performance issues so let's say is OK.
Setter and performances
Now it would be nice also to being able to modify my member, let's implement a setter, but before ...
Thinking about it, I see no way a setter could as performant as a direct member access:
- it will certainly use a copy or copy assignment constructor
- if it uses a move constructor, it makes the API more tricky and in a way client API will have to construct a new object
- there are certainly integrity checks to be done (if not what is the point of encapsulation ?)
I read here and then that compilers are optimizing things, but for me it is not granted, depending on the type implementations, and maybe platform dependent (I will try to dig more those aspects).
And even If I am not wanting top notch performance, I for sure do no want to add unnecessary copies at this early stage of development.
Encapsulation, the right way ?
A response on a S.O post enlightened me:
https://stackoverflow.com/questions/13853424/getters-and-setters-is-there-performance-overhead
Getters and setters are signs that your class isn't designed in a useful way: if you don't make the outer behavior abstract from the internal implementation, there's no point in using an abstract interface in the first place, you might as well use a plain old struct.
Think about what operations you really need. That's almost certainly not direct access ...
It remembered me the quote of my colleague: if we are using a class and not a struct, data members have dependencies so we may not want to let them being modified individually.
I finally stepped back and asked me again: what was my class needed for ? Do I really need to own those 'complex' objects ? The answer was no and I ended up with another design, a design that encapsulates and exposes only what is needed, with no setters and getters.
Takeaways
This could clearly evolve with time, but my current advice, for those undecided between the two extremes that are structs and encapsulation:
- if members are not dependent on each other, and no global integrity is needed, favor structs
- in any other cases, encapsulate and expose only methods that do no compromise the integrity of the whole class and its precious private data members
- encapsulation does not mean necessarily getters and setters. Both of them could come with costs like lower integrity and performance.
Top comments (2)
Thanks for sharing. I think you meant Scott Meyers though ;)
Yes thank you ! And sorry about that :)