Помимо технических деталей, макет памяти<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);
}