Помимо технических деталей, макет памяти<optional<T>>меньше:
template <typename T>
class optional
{
bool _initialized;
std::aligned_storage_t<sizeof(t), alignof(T)> _storage;
};
Но для целей этого анализа, учитывая макеты памяти, мы можем думать об этом как:
template <typename T>
class optional
{
bool _initialized;
T _storage;
};
<optional<int>>, а если предположить, что<sizeof(int)==
4>, то получим<sizeof(optional<int>)
==8>. Это так из-за правил выравнивания, для наших двух членов мы получаем следующее выравнивание:

Это означает, что вы можете поместить в два раза больше<int>с, чем<optional<int>>с в том же пространстве памяти. Поэтому, если размер объектов имеет решающее значение для вашего приложения (например, потому что вы хотите использовать кэш процессора, чтобы получить производительность), и вы определили, что готовы торговать ясностью кода, рекомендуется просто перейти с типом<int>и использовать некоторое «магическое значение» для представленияnon-an-intили использовать что-то вроде<markable>библиотеки.
Даже если вы не можете сохранить какое-либо значение<int>для представленияне-int(например, потому что каждое значение полезно, или вы хотите подать сигналне-an-intявно), по крайней мере для<Trivial>типов вы должны рассмотреть возможность хранения значения и<bool>флага, представляющегонулевое состояниеотдельно. Рассмотрим следующий класс:
struct Record
{
optional<int> _min;
optional<int> _max;
};
Его макет памяти можно изобразить следующим образом:

Это как если бы у нас были следующие члены:
struct Record
{
bool _has_min;
int _min;
bool _has_max;
int _max;
};
Но когда они хранятся отдельно, у нас, по крайней мере, есть возможность переупорядочения их так:
struct Record
{
bool _has_min;
bool _has_max;
int _min;
int _max;
};
Что дает нам следующий макет (и меньший общий размер):

Иногда это требует детального рассмотрения того, какие данные мы делаем необязательными. В нашем случае выше, если мы определим, что минимальное и максимальное значение может быть предоставлено или не предоставлено вместе, но одно никогда не предоставляется без другого, мы можем сделать только один дополнительный мембр:
struct Limits
{
int _min;
int _max;
};
struct Record
{
optional<Limits> _limits;
};
Это даст нам следующую схему:

Наличие функциональных параметров типа<const
optional<T>&>может повлечь за собой определенные непредвиденные затраты времени выполнения, связанные с копированием конструкции<T>. Рассмотрим следующий код.
void fun(const optional<Big>& v)
{
if (v) doSomethingWith(*v);
else doSomethingElse();
}
int main()
{
optional<Big> ov;
Big v;
fun(none);
fun(ov);
fun(v);
}
Никакое устранение копии или семантика перемещения не может спасти нас от копирования<Big>здесь. Не то, чтобы мы нуждались в какой-либо копии, но вот как это работает<optional>. Во избежание копирования в этом случае можно обеспечить вторую перегрузку<fun>:
void fun(const Big& v)
{
doSomethingWith(v);
}
int main()
{
optional<Big> ov;
Big v;
fun(ov);
fun(v);
}
В качестве альтернативы вы можете рассмотреть возможность использования дополнительной ссылки вместо:
void fun(optional<const Big&> v)
{
if (v) doSomethingWith(*v);
else doSomethingElse();
}
int main()
{
optional<Big> ov;
Big v;
fun(none);
fun(ov);
fun(v);
}