2015-01-25 2 views
8

Рассмотрим этот класс:Какой тип объекта должен возвращать эта функция?

class Widget 
{ 
    Widget::Widget(); 
    bool initialize(); 
} 

Widget имеет следующие характеристики:

  1. initialize() должен быть вызван, чтобы полностью построить
  2. initialize() может не
  3. initialize() дорого

Учитывая, что я герметизирующего создание в функции фабрики, которая всегда возвращает тот же экземпляр Widget:

Widget* widget() { 
    static auto w = new Widget; 
    static auto initialized = false; 

    if (!initialized) { 
     if (!w->initialize()) { 
      return nullptr; 
     } 
     initialized = true; 
    } 

    return w; 
} 

Что следует возвращаемый тип widget() быть?

В частности, я хотел бы как-то пояснить, что срок службы возвращаемого Widget пережитит любого вызывающего абонента, но без ссылки на внутреннюю реализацию.

  1. Возвращает необработанный указатель и добавляет комментарий, который гласит: «Возвращаемый указатель указывает на объект со статическим временем хранения, который не будет удален до конца программы». Это просто, но не самодокументируемо.
  2. Возврат std::shared_ptr<Widget>. Это самодокументируется, но мне не нравится, что он внесет совершенно ненужные накладные расходы на подсчет ссылок.
  3. Возврат std::unique_ptr<Widget> с пользовательской функцией удаления, которая не работает. Я думаю, что такая же проблема воспринимается как # 2, если вызывающий преобразует ее в shared_ptr.
+0

Это действительно вопрос мнения, но в данном конкретном случае я не стал бы беспокоиться о пересчете накладных расходов. Если ваш тип виджета «дорог», чтобы создать, то любые «ненужные затраты на подсчет ссылок», вероятно, будут тривиальными по сравнению. – MrEricSir

+0

Язык C++ не поощряет использование интерфейсов. Но да, они - полная лаваша, чтобы отлаживать, разоблачать реализацию сложно. –

+2

Должны ли мы игнорировать многопоточность? (Даже в C++ 11 это небезопасно) –

ответ

11

Я голосую за:

boost::optional<Widget&> widget() { 
    static Widget w; // no reason for this to be a pointer 
    static bool initialized = false; 

    if (!initialized) { 
     if (!w.initialize()) { 
      return boost::none; 
     } 
     initialized = true; 
    } 

    return w; 
} 

Это ясно показывает, что абонент не владеет Widget в любом случае, нет никакого беспокойства вызывающего delete -ную Widget, и это ясно, будет ли или не удалось выполнить вызов.

+0

Как насчет в среде без Boost? –

+1

@JoshuaJohnson Вы можете просто скопировать «необязательный» оттуда - это только библиотека заголовка. Также есть ['std :: experimental :: optional'] (http://en.cppreference.com/w/cpp/experimental/optional). Или вы можете просто написать свой собственный класс, у которого есть возможно-нулевой член 'Widget * ', который вы должны разыменовать, чтобы выйти. – Barry

+1

экспериментальная опция не имеет ссылочную поддержки, наконец, я проверил. – Yakk

7

Не является ли исходный указатель правильной вещью? Он уже выражает ограничения. Он может потерпеть неудачу (возвращая nullptr), и поскольку он не дает никаких обещаний относительно указателя, вызывающие абоненты не могут безопасно его удалить. Вы получаете необработанный указатель, вы не можете сделать предположение, что вам разрешено делать какие-либо утверждения о времени жизни объекта с указателем.

+0

Я думаю, что исходный указатель в порядке. Он документирует тот факт, что вызывающий абонент не должен владеть, и он может быть нулевым. Я думаю, что любой вариант выбран, он все еще нуждается в разъяснении с комментариями или соглашениями об именах, что это Синглтон, поэтому срок жизни ясен. –

1

Рекомендация Херба Саттера в этом случае (пункт 4 в http://herbsutter.com/2013/05/30/gotw-90-solution-factories/) должна вернуть optional.

Возможно, есть еще одна причина, по которой функция могла бы вернуть указатель, а именно вернуть nullptr, чтобы указать на неспособность произвести объект. Как правило, лучше исключить сообщение об ошибке, если мы не загрузим виджет. Однако, если вы не можете загрузить виджет, это нормальная работа и не должна считаться ошибкой, возвратите опцию и, возможно, сделайте фабрику noexcept, если не нужно сообщать о каких-либо других ошибках, которые хорошо передаются, возвращая пустую необязательную ,

+0

Разве Herb не говорит о случае, когда вызывающий получает право собственности на возвращаемый объект? –

+0

А, да, хорошая точка. Интересно, лучше ли удалять этот ответ? –

0

Для того, чтобы вся жизнь и собственность яснее я бы использовать соглашение о Singleton pattern и сделать функцию статической getInstance функции на Widget класса.

class Widget { 
    bool initialize(); 
public: 
    static Widget* getInstance() { 
    static Widget w; 
    static bool initialized = false; 

    if (!initialized) { 
     if (!w.initialize()) { 
     return nullptr; 
     } 
     initialized = true; 
    } 
    return &w; 
    } 
}; 

Я думаю сырой возвращаемый тип указателя документирует факт, что абонент не ожидаются, взять на себя ответственность, и это может быть пустым.

1

Как отмечают другие люди, если завод будет производить только один товар, завод, возможно, не является правильным термином. Кажется, Синглтон.

Принимая во внимание, что:

  • Мы создадим только один экземпляр виджета
  • Этот экземпляр будет построен первый раз, когда кто-то спросить за это (если таковые имеются)
  • Этот экземпляр не будет жить до тех пор, конец программы и должны быть уничтожены, а затем
  • Никто не должен удалить экземпляр

Постараюсь somethi ng вот так:

class Widget { 
public: 
    static Widget& Instance() { 
     static Widget w{}; 
     return w; 
    } 

private: 
    Widget() { 
     // Expensive construction 
    } 
    Widget(const Widget&) = delete; // avoid copy 

}; 
+0

Это нормально, но не справляется с ситуацией, когда инициализация может завершиться неудачей. –

Смежные вопросы