C++17 has introduced a very useful template class, std::optional
:
The class template
std::optional
manages an optional contained value, i.e. a value that may or may not be present.
I have been using this feature a lot since 2017, even for a simple optional integer. Today, I got curious: is this template type effective compared to an equivalent type that I would write myself to achieve the same behavior?
Good question, thank you 😆
Let's try to write some code and use Compiler Explorer to compare both solutions: std::optional
vs custom type for optional value.
First, let's define two equivalent types:
#include <optional>
using StdOptionalInt = std::optional<int>;
struct CustomOptionalInt {
bool has_value;
int value;
};
Then, let's write two functions that test if the value is available and return either this value (if available) or a default value (if not available):
int getStdOptional(const StdOptionalInt& o) {
return o.has_value() ? o.value() : 0;
}
int getCustomOptional(const CustomOptionalInt& o) {
return o.has_value ? o.value : 0;
}
Finally, compile the code with Compiler Explorer and compare the output assembly codes (you can try by yourself here):
Yeah! 😃 The 2 functions generate the same assembly code. There is no difference of performance. Notice the static assertion at the end of the source code: because the code compiles, it means the footprint are the same. The behavior is the same with gcc and clang for x86-64.
As a conclusion, std::optional
is as efficient as a custom type to represent an optional integer value. Don't implement your own type, simply use the standard type. You may even get better performance using std::optional
, as explained on cppreference:
As opposed to other approaches, such as
std::pair<T,bool>
,optional
handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.
By the way, if you happen to speak French:
- I wrote an article to explain to how to use std::optional.
- I made a video to present Compiler Explorer.
Top comments (2)
Stunning that the generated code is the same. Have you had a look into one od the standard implementations?
github.com/gcc-mirror/gcc/blob/mas...
To be honest, I wasn't surprised. I was not expecting the exact same code but I was expecting something similar, for various reasons:
has_value()
is just about returning abool
. No reason to cost more than a simple access to member variable.value()
is just a test to decide to return the value or throw an exception. Nothing fancy here.We may expect the exception to cost a more than a simple access to a member variable. So why ?
This leads me to reason 3. Over the years (and hours spent in Compiler Explorer, you definitively must use it!), I have learned something important: modern compilers are amazing at optimizing code. Way much better that you and me.
And here, take a better look at the code: it tests if the
std::optional
has a value and get the value conditionally. The compiler then know the exception cannot be thrown and optimizes that out.Change the code and get a completely different result:
This generates all the code to handle the exception and it's a lot a code.
I something take a look at the source of standard features. I simply to a CTRL+click in my IDE.
std::optional
seems crazy but it does much more than my custom type. It is made by and for the standard library developers. And this leads me to reason 4 : these developers are way better than you and me to create powerful, flexible, easy-to-use, robust code ;)