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