Косвенный член 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, просто выберите один или другой в каждом случае, и в тех местах, где вы могли бы воспользоваться "универсальным интерфейсом", передайте тип в качестве параметра шаблона при необходимости. А в областях, критичных к производительности, сделайте соответствующий выбор вручную.

Другие вопросы по тегам