Косвенный член RAII: unique_ptr или необязательный?
Рассмотрим класс с членом, который не может быть сохранен напрямую, например, потому что у него нет конструктора по умолчанию, а у конструктора вложенного класса недостаточно информации для его создания:
class Foo
{
public:
Foo(){} // Default ctor
private:
/* Won't build: no default ctor or way to call it's
non-default ctor at Foo's ctor. */
Bar m_bar;
};
Очевидно, что m_bar
должен храниться по-разному, например, через указатель. std::unique_ptr
кажется лучше, хотя, поскольку это разрушит это автоматически:
std::unique_ptr<Bar> m_bar;
Также возможно использовать std::experimental::optional
, хоть:
std::experimenatl::optional<Bar> m_bar;
Мои вопросы: 1. Каковы компромиссы? и 2. Имеет ли смысл создавать класс, автоматизирующий выбор между ними?
В частности, глядя на исключения исключения для Ctor of std::unique_ptr
и гарантии исключения для ctor std::experimental::optional
Кажется очевидным, что первый должен выполнять недостатки динамического выделения и освобождения - скорости выполнения, а второй хранит вещи в некоторых (выровненных) недостатках размера буфера памяти. Это единственные компромиссы?
Если это действительно компромиссы, и учитывая, что оба типа имеют достаточный общий интерфейс (ctor, operator*
), имеет ли смысл автоматизировать выбор между ними чем-то вроде
template<typename T>
using indirect_raii = typename std::conditional<
// 20 - arbitrary constant
sizeof(std::experimental::optional<T>) >
20 + sizeof(std::exerimental::optional<T>)sizeof(std::unique_ptr<T>),
std::unique_ptr<T>,
std::experimental::optional<T>>::type;
(Примечание: есть вопрос, обсуждающий компромиссы между этими двумя типами возвращаемых данных, но вопрос и ответы сосредоточены на том, что каждый из них передает вызывающим функциям, что не имеет значения для этих частных членов.)
1 ответ
ИМО здесь есть другие компромиссы:
unique_ptr
не копируется и не может быть назначенoptional
является.
Я полагаю, вы могли бы сделать одну вещь:indirect_RAII
тип класса и условно добавить определения, чтобы сделать его копируемым путем вызоваBar
копирует ctor, даже когдаunique_ptr
выбран. (Или наоборот, отключите копирование, когда это необязательно.)optional
типы могут иметьconstexpr
конструктор - вы не можете сделать эквивалентную вещь сunique_ptr
во время компиляции.Bar
может быть неполным в то время, когдаunique_ptr<Bar>
построен Это не может быть неполным в то время, когдаoptional<Bar>
известен. В вашем примере я предполагаю, что вы предполагаете, чтоBar
завершен, так как вы берете его размер, но потенциально вы можете реализовать класс, используяindirect_RAII
где это не так.- Даже в тех случаях, когда
Bar
большой, вы все еще можете обнаружить, что, например,std::vector<Foo>
будет работать лучше, когдаoptional
выбран, чем когдаunique_ptr
является. Я ожидаю, что это произойдет в тех случаях, когдаvector
заполняется один раз, а затем повторяется много раз.
Может быть, как правило, ваше правило размера подходит для общего использования в вашей программе, но я полагаю, что для "общего использования" не имеет значения, какой из них вы выберете. Альтернатива использованию вашего indirect_RAII
type, просто выберите один или другой в каждом случае, и в тех местах, где вы могли бы воспользоваться "универсальным интерфейсом", передайте тип в качестве параметра шаблона при необходимости. А в областях, критичных к производительности, сделайте соответствующий выбор вручную.