Magic values in the code: sometimes they are used as an ill-advised solution for the problem of not being able to initialize a variable. Functional programmers may ask: “If you cannot initialize it, why did you put it in this scope at all?” Well, the duteous procedural programmer ignores this question and shivers when encountering magic values during code review. Code like the following snippet silently produces bugs waiting to happpen TM and with increasing complexity reduce the comprehensibility.
A baroque solution for this problem is the usage of a pointer. In
C++, the value must be owned somewhere as a std::unique
That is a solution to work with, since now we have an additional state
that represents an uninitialized value. To be concrete,
std::nullptr as pointer value is now distinct from the value of the
pointee. But the elephant in the room is: to achieve this, we require
heap allocation (for things that may be as small as a
std::pair<T,bool> can do the same for us without
requiring the heap. Once more, uninitialized values can be expressed
explicitly. But for this solution to work we need
T to be
default-constructible, which is not always the case (apart from
std::pair<T,bool> being quite cumbersome).
C++17 is an option for your code,
optional<T> is the
construct of choice. An idiomatic solution at last, just that is a
striking advantage on its own. See below some illustrations on how to
use it practice. The code cuts out some non-interesting parts. Since
it has just been accepted into the
C++17 standard, you need
to compile it.
The example below illustrates inplace construction of a member variable. Of course, we do not need to initiate it in the constructor (which is the entire point of
optional). Thus, optional is a good way of implementing two-step initialization, although my stance on it is to avoid it whenever you can. In the boost-world, you can achieve the same using inplace-factories.
In a naturally way, we can use
optionalin conjunction with default arguments (see
function1). What is
nulloptin the new standard corresponds to
optionalas a return value of
function2. Only use this, if the non-value
nulloptactually represents a meaningful return value. Do not use it for ensuring dead code consistency (i.e., returning
default:label in a
switchstatement, although you have listed all possible values).
Then there is
emplace()which allows for inplace-construction of a value replacing the current value. Notice, that this is more efficient than
*_heavyObject = HeavyObject(...), which comes at the cost of an additional move operator invocation and an additional destructor invocation.
Pointers versus optional
In the example above, you probably noticed that
optional<T> is mostly
used the same way as a pointer. The operators
operator-> are implemented such that
pretty much can be used like a pointer.
In stark contrast to pointers however, the value of an
is not allocated on the heap. Typically,
implemented as discriminated union light:
there is a member
bool(to indicate whether present)
Tand a dummy
char(for avoiding that the object needs to be default constructible).
So while we can use it like a pointer, it actually has value semantics
and copy and move operations for
optional<T> are as expensive as for
the underlying type
T. In conclusion,
sizeof(T) + 1 plus padding bytes. Keep that in mind, it means 16
optional<double> on my machine.
“All of this sounds wonderful!”, you might think. “Why would I ever
want to use a pointer at all?” Well, as the value is kept on the
T cannot be an abstract type. Furthermore, because of value
semantics, shared ownership of the value is not possible. Thus,
optional<T> takes over a domain for which we previously needed to
abuse pointers. There is no gray area in between the two and it should
always be clearly decidable when to use what. Without deep meditation
on the topic, I would argue that even
unique_ptr<optional<T>> are considered good practice as they keep
the heap allocation/ownership aspect and the optional aspect separate
(given that both aspects are really needed).
As a closing note, be careful and considerate when using