Thoughts about getters and setters in C++

This article deals with getters and setters in C++. I am sorry, it is not about coroutine, but part 2 of thread pools will come in the following weeks.

TL;DR: Getters and setters are bad for structure like objects.

Introduction

In this article, I will only give my opinion about them, I don’t want to offend anyone, I am just going to explain why, or why not, use getters and setters. I would be glad to have any debates in the comments part.

Just to be clear about what I am talking about when I talk about getter, I talk about a function that just returns something, and when I talk about setter, I talk about a function that just modifies one internal value, not doing any verification or other computations.

Getter performances

Let’s say we have a simple structure with their usual getter and setters:

Let’s compare this version with the one without getters and setters.

It is incredibly shorter and less error_prone. We can not make the error to return the last name instead of the first name.

Both codes are fully functional. We have a class Person with the first name, the last name, and an age. However, let’s say we want a function that returns the presentation of a person.

Show the performance differences between computation using getters by value and public access.

The version without getters performs this task 30% quicker than the version with getters. But why? It is because of the return by value of the getters functions. Returning by value makes a copy that results in poorer performance. Let’s compare the performance between person.getFirstName(); and person.firstName.

Show the performance differences between getters by value and public access.

As you can see, accessing directly the first name instead of a getter is equivalent to a noop.

Getter by const reference

However, it is possible to not return by value but return by reference instead. Going that way, we will have the same performance as without getters. The new code will look like that

Since we get the same performance as before, are we done? To answer this question, you can try to execute this code.

You may see some weird characters wrote in the console. But why? What happened when you do make().getLastName()?

  1. You create a Person
  2. You get a reference to the last name
  3. You delete Person

You have a dangling reference! It can lead to a crash (in the best case) or something worst than what can be found in a horror movie.

To deal with such a thing, we must introduce reference qualified functions.

Here is the new solution that works everywhere. You need 2 getters. One for lvalue and one for rvalue (both xvalue and prvalue).

Setter issues

There is not a lot to say in this section. If you want to achieve the best performances, you must write a setter that takes a lvalue and one that takes a rvalue. However, it is generally fine to just have a setter that takes a value that will be moved. Nevertheless, you have to pay the price of an extra move. However, that way, you cannot just make a little enhancement. You must replace the whole variable. If you just wanted to replace one A by a D in a name, it will not be possible by using setters. However, using direct access makes it possible.

What about immutable variables?

Ones will tell you to just make the member attribute as const. However, I am not ok with this solution. Indeed, making it const will prevent the move semantic and will lead to unnecessary copy.

I have no magic solution to propose you right now. Nevertheless, we can write a wrapper which we can named immutable<T>. This wrapper must be able to be :

  1. Constructible
  2. Since it is immutable, it must not be assignable
  3. It can be copy constructible or move constructible
  4. It must be convertible to const T& when being a lvalue
  5. It must be convertible to T when being a rvalue
  6. It must be used like other wrapper through operator* or operator->.
  7. It must be easy to get the address of the underlying object.

Here is a little implementation

So, for an immutable Person, you can just write:

Conclusion

I would not say that getters and setters are bad. However, when you do not need to do anything else in your getter and your setter, achieving the best performance, safety and flexibility lead to writing:

  • 3 getters (or even 4): const lvalue, rvalue, const rvalue, and if you want, non-const lvalue (even if sounds really weird since it is easier to just use direct access)
  • 1 setter (or 2 if you want to have the maximum performance)

It is a lot of boilerplate for almost anything.

Some people will tell you that getters and setters enforce encapsulation, but they don’t. Encapsulation is not just making attributes private. It is about hiding things from users, and for structure-like objects, you rarely want to hide anything.

My advice is: when you have a structure like objects, just don’t use getter and setters and go with public / direct access. To be simple, if your setter does not enforce any invariant, you don’t need a private attribute.

PS: For people who use libraries using shallow copy, the performance impact is less important. However, you still need to write 2 functions instead of 0. Don’t forget, the less code you write, the less bugged it will be, easier to maintain, and easier to read.

And you ? Do you use getters and setters? And why?

C++ error handling, let’s abuse the co_await operator

Introduction

Sometimes (very rarely :-p), errors may happen. It can be due to misuse from the user, it can be due to a system error, a corrupted, or a missing file. Errors can pop from everywhere. There are several ways to handle errors. In this article, we are going to see how coroutine may be used for error handling, so let’s abuse the co_await operator

Old ways to handle errors

One of the first ways to handle errors used (AFAIK) was the error code return. The idea is simple, you return OK when the function executes properly, else you return an error code, like OUT_OF_RANGE, FILE_NOT_EXIST

You ended with code like:

As expected, the code will write error! and 10.

The advantage of this way is that it is really explicit, you know which function can fail. A disadvantage is that there is no propagation of errors. You must treat error in the same place that you treat the correct path. Another problem is it can lead to severe problems if the client of your function does not check if it fails or not. Believe me, programmers are somewhat lazy, and they may forget to check the return value and they will process the result like if it was correct. For example, let’s imagine you developed an initialize() function, it returns fails, but you use the object not initialized. it will, sooner or later, lead to a severe failure.

Another way to process errors is the exception. When you detect an error, you throw the error, and when you want to process the error, you catch it. It solves the problems both of error propagation and the use of non initialized objects we saw prior. The code will look like that:

The code is shorter, and the error handling is done when you need it. However, it is difficult to know if the function f can fail if we don’t have the code of g(). We can doubt because f is not noexcept, but that’s all, and it does not give us so much information about the possible error.

A modern way for error-handling thanks to ADT

There is a proposal for an expected object for C++23. Basically, std::excpected is a template object which takes 2 arguments.

  1. The result type
  2. The error type

It is a sum-type, so it can contain either the result or the error. However, the std::expected way to handle errors will be a bit like the error code, you don’t get automatic propagation. However, you may use the functional approaches that simulate the error propagation:

The map function will be executed only if g(success) is not an error, if it is an error, the error will be propagated to the caller.

All the lambda thing is very good, works perfectly, is pretty_fast and readable. However, in some cases, it can become cumbersome.

In the rust language programming, we would write something like:

Note the presence of the operator ?. It means, if g(success) succeed, so continue the execution, else, stops the execution and propagates the error to the caller.

Did this story of stopping and continue the execution reminds you something?

Let’s abuse the co_await operator !

The objective will be to be able to write something like:

You can even imagine a macro try or TRY to make things even better :p. But be careful if you are using exceptions :).

Let’s design a simple Expected class.

I didn’t use reference qualified member for the sake of simplicity. However, in a production code, to have the best performance, you must use them to avoid useless copy etc.

I use a std::variant with std::monostate because we are going to need it later. So, basically, we have a class that represents either a result or an error. You have a function to ask which value is carried by the Expected and you have a function to retrieve the result or the error.

As we said before, Expected is meant to be used with coroutines. It must have a nested promise_type

The promise_type

We remind that the promise_type must have 5 member functions.

  1. get_return_object() which will return an expected
  2. return_value() / return_void() which will handle the co_return operator.
  3. initial_suspend and final_suspend that handle the beginning and the end of the coroutine
  4. unhandled_exception that handles unhandled exceptions.

In our example, unhandled_exception will do nothing for the sake of simplicity. initial_suspend and final_suspend will be of std::suspend_never because when we launch the function, we want it to not be paused, and when we exit the function, we expect everything to be cleared properly.

Let’s talk about the get_return_object() and return_value(). We are going to begin with return_value(). Its prototype will be something like void return_value(Expected result). We can write different overloads for Result and Error and their reference qualified friends, but for the sake of simplicity, again, I chose to have only the Expected overload :-).

We must do something with this result, we must set the current expected with this value. To do that, I decided to use a pointer on the current Expected instance.

For the get_return_object function, things are not that easy. You must be able to construct an expected without an error or a result. Moreover, you must initialize the pointer to the expected in the promise_type.

Then, I added a private constructor to the Expected object.

The promise_type is as we described prior.

However, be careful with your get_return_object function. Here it works because of guaranteed copy elision. If there was no elision, you will get a segmentation fault(in the best case) because the Expected address will not be the same 🙂

Our Expected object can be co_returned from a coroutine, but it can not be co_awaited . So, let’s abuse the co_await operator !

Awaiter

To make our Expected class awaitable, we must define an Awaiter class.

As a reminder, an awaiter must have three functions.

  1. await_ready: which returns a bool to know if we can continue the execution, or suspend it.
  2. await_resume: which returns the type wanted from co_await x.
  3. await_suspend: which is called when a coroutine is suspended.

The await_ready is really simple. If the expected is an error, we suspend, else, we continue. The await_resume function just returns the result. The await_suspend function is the most complicated! It is called when we have an error, it must give the error to the expected returned by get_current_object. Moreover, it must destroys the current coroutine.

Hence, here is the code for Awaiter class and the operator co_await:

Again, I do not manage reference qualified methods. You must do it in production code. Here is the full code if you want it.

Performance ?

It is purely one hypothesis, but I do believe that in the future, the compiler could be optimized out of this kind of code. Indeed, from cpp-reference, we can read :

  • The lifetime of the coroutine state is strictly nested within the lifetime of the caller, and
  • the size of coroutine frame is known at the call site

The first is obvious, the second I think yes, but I am not sure, that is why it is one hypothesis.

Thanks for reading :).

Blog talking about 3D rendering, Qt and C++