1
2[section Performance considerations]
3
4Technical details aside, the memory layout of `optional<T>` for a generic `T` is more-less this:
5
6  template <typename T>
7  class optional
8  {
9    bool _initialized;
10    std::aligned_storage_t<sizeof(t), alignof(T)> _storage;
11  };
12
13Lifetime of the `T` inside `_storage` is manually controlled with placement-`new`s and pseudo-destructor calls. However, for scalar `T`s we use a different way of storage, by simply holding a `T`:
14
15  template <typename T>
16  class optional
17  {
18    bool _initialized;
19    T _storage;
20  };
21
22We call it a ['direct] storage. This makes `optional<T>` a trivially-copyable type for scalar `T`s. This only works for compilers that support defaulted functions (including defaulted move assignment and constructor). On compilers without defaulted functions we still use the direct storage, but `optional<T>` is no longer recognized as trivially-copyable. Apart from scalar types, we leave the programmer a way of customizing her type, so that it is reconized by `optional` as candidate for optimized storage, by specializing type trait `boost::opitonal_config::optional_uses_direct_storage_for`:
23
24  struct X // not trivial
25  {
26    X() {}
27  };
28
29  namespace boost { namespace optional_config {
30
31    template <> struct optional_uses_direct_storage_for<X> : boost::true_type {};
32
33  }}
34
35
36[heading Controlling the size]
37
38For the purpose of the following analysis, considering memory layouts, we can think of it as:
39
40  template <typename T>
41  class optional
42  {
43    bool _initialized;
44    T _storage;
45  };
46
47Given type `optional<int>`, and assuming that `sizeof(int) == 4`, we will get `sizeof(optional<int>) == 8`. This is so because of the alignment rules, for our two members we get the following alignment:
48
49[$images/opt_align1.png]
50
51This means you can fit twice as many `int`s as `optional<int>`s into the same space of memory. Therefore, if the size of the objects is critical for your application (e.g., because you want to utilize your CPU cache in order to gain performance) and you have determined you are willing to trade the code clarity, it is recommended that you simply go with type `int` and use some 'magic value' to represent ['not-an-int], or use something like [@https://github.com/akrzemi1/markable `markable`] library.
52
53Even if you cannot spare any value of `int` to represent ['not-an-int] (e.g., because every value is useful, or you do want to signal ['not-an-int] explicitly), at least for `Trivial` types you should consider storing the value and the `bool` flag representing the ['null-state] separately. Consider the following class:
54
55  struct Record
56  {
57    optional<int> _min;
58    optional<int> _max;
59  };
60
61Its memory layout can be depicted as follows:
62
63[$images/opt_align2.png]
64
65This is exactly the same as if we had the following members:
66
67  struct Record
68  {
69    bool _has_min;
70    int  _min;
71    bool _has_max;
72    int  _max;
73  };
74
75But when they are stored separately, we at least have an option to reorder them like this:
76
77  struct Record
78  {
79    bool _has_min;
80    bool _has_max;
81    int  _min;
82    int  _max;
83  };
84
85Which gives us the following layout (and smaller total size):
86
87[$images/opt_align3.png]
88
89Sometimes it requires detailed consideration what data we make optional. In our case above, if we determine that both minimum and maximum value can be provided or not provided together, but one is never provided without the other, we can make only one optional memebr:
90
91  struct Limits
92  {
93    int  _min;
94    int  _max;
95  };
96
97  struct Record
98  {
99    optional<Limits> _limits;
100  };
101
102This would give us the following layout:
103
104[$images/opt_align4.png]
105
106[heading Optional function parameters]
107
108Having function parameters of type `const optional<T>&` may incur certain unexpected run-time cost connected to copy construction of `T`. Consider the following code.
109
110  void fun(const optional<Big>& v)
111  {
112    if (v) doSomethingWith(*v);
113    else   doSomethingElse();
114  }
115
116  int main()
117  {
118    optional<Big> ov;
119    Big v;
120    fun(none);
121    fun(ov); // no copy
122    fun(v);  // copy constructor of Big
123  }
124
125No copy elision or move semantics can save us from copying type `Big` here. Not that we need any copy, but this is how `optional` works. In order to avoid copying in this case, one could provide second overload of `fun`:
126
127  void fun(const Big& v)
128  {
129    doSomethingWith(v);
130  }
131
132  int main()
133  {
134    optional<Big> ov;
135    Big v;
136    fun(ov); // no copy
137    fun(v);  // no copy: second overload selected
138  }
139
140Alternatively, you could consider using an optional reference instead:
141
142  void fun(optional<const Big&> v) // note where the reference is
143  {
144    if (v) doSomethingWith(*v);
145    else   doSomethingElse();
146  }
147
148  int main()
149  {
150    optional<Big> ov;
151    Big v;
152    fun(none);
153    fun(ov); // doesn't compile
154    fun(v);  // no copy
155  }
156[endsect]
157