DEV Community

lsioctl
lsioctl

Posted on • Updated on

C++ encapsulation, a tale of getters and setters ?

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)
Enter fullscreen mode Exit fullscreen mode

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_;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (2)

Collapse
goldenxp profile image
Sunder Iyer

Thanks for sharing. I think you meant Scott Meyers though ;)

Collapse
lsioctl profile image
lsioctl Author

Yes thank you ! And sorry about that :)