Две Конструкции фазы в C++

Я имею как часть присвоения для изучения комплекта разработчика, который использует "двухфазную" конструкцию для классов C++:

// Include Header
class someFubar{
public:
    someFubar();
    bool Construction(void);
    ~someFubar();
private:
    fooObject _fooObj;
}

В источнике

// someFubar.cpp
someFubar::someFubar : _fooObj(null){ }

bool 
someFubar::Construction(void){
    bool rv = false;
    this->_fooObj = new fooObject();
    if (this->_fooObj != null) rv = true;
    return rv;
}

someFubar::~someFubar(){
    if (this->_fooObj != null) delete this->_fooObj;
}

Почему был бы это "двухфазное" использоваться и что преимущества там? Почему не только инстанцируют объектной инициализации в фактическом конструкторе?

11
задан t0mm13b 11 June 2010 в 09:11
поделиться

8 ответов

Документ о Двухфазном строительстве.

Идея заключается в том, что вы не можете вернуть значение из конструктора, чтобы указать на неудачу. Единственный способ указать на неудачу конструктора - выбросить исключение. Это не всегда желательно, не в последнюю очередь потому, что безопасность исключений - очень сложная тема.

Поэтому в данном случае конструкция разделяется: конструктор, который не выбрасывается, но и не инициализируется полностью, и функция, которая выполняет инициализацию и может вернуть признак успеха или неудачи без (обязательного) выбрасывания исключений.

19
ответ дан 3 December 2019 в 01:13
поделиться

Нет никаких веских причин делать это - избегайте. Автор кода, вероятно, просто не знал, что делает.

6
ответ дан 3 December 2019 в 01:13
поделиться

Приведенный источник крайне причудлив.

Если это C++, то new не может возвращать 0 и должен выбрасывать исключение.

Если это что-то не совсем непохожее на C++, где new возвращает 0 при неудаче (такие вещи известны), то Construction эквивалентна:

bool someFubar::Construction(){ // no need for void in C++, it's not C
    delete _fooObj;             // nothing stops this getting called twice!
    _fooObj = new fooObject();  // no need for this->, it's not Python
    return _fooObj;             // non-zero is true 
}

someFubar::~someFubar(){
    delete fooObj;              // it's OK to delete NULL in C++. 
}

Теперь у нас проблема - если Construction вызывается дважды, конструируем ли мы его снова и возвращаем true, как сказано выше, не конструируем ли мы его снова и возвращаем true, поскольку он построен, или не конструируем его снова и возвращаем false, поскольку конструировать что-то дважды - ошибка?

Иногда - например, для классов, которые управляют внешними ресурсами - вы можете захотеть реализовать их в виде машины состояний, которую вы создаете, а затем выделяете ресурс. Это целесообразно в тех случаях, когда ресурс может стать недействительным, так что вам в любом случае придется проверять операции, которые вы выполняете над ним. Предоставление преобразования из типа в bool является шаблоном, используемым в стандартной библиотеке для таких ресурсов. Обычно вы должны создать объект за один раз и ожидать, что он будет действительным.

1
ответ дан 3 December 2019 в 01:13
поделиться

Иногда приходится использовать C++ без включенных исключений. В такой ситуации отсутствие возвращаемого конструктором значения усложняет жизнь, поэтому вместо этого у вас есть явная функция конструирования, которую вы можете проверить на успех.

2
ответ дан 3 December 2019 в 01:13
поделиться

Причиной для этого может быть то, что конструкторы не возвращают никаких значений. Некоторые предпочитают делать Create как функцию, которая должна вызываться после инстанцирования объекта. В случае, если вы не используете исключения, потому что они создают много кода (особенно в мире embedded), невозможно использовать только конструктор, потому что он не дает никаких гарантий (как я уже сказал, он не может возвращать никаких значений).

Другая техника, которую я видел, была примерно такой:

XObject* o = new XObject();
o->IsOk();

Она в основном выполняет конструирование в конструкторе и хранит результат операции в переменной - это не экономит место.

1
ответ дан 3 December 2019 в 01:13
поделиться

Зачем использовать эту «двухфазную» и какие преимущества от нее?

Эта идиома используется по двум причинам:

  • для разделения инициализации на две разные части: часть, которая всегда стабильна и не выйдет из строя (оставлена ​​в конструкторе), и часть, которая может выйти из строя (отдельная функция построения).

Эта идиома использовалась в прошлом до того, как использование исключений было стандартизировано в языке, а также разработчиками библиотек, которые не понимали (или по какой-то причине не хотели использовать) исключения.

Вы по-прежнему найдете его в устаревшем / обратно совместимом коде (например, MFC).

  • для реализации «виртуального строительства». По сути, вы объявляете свою функцию-член Construction как (чистую) виртуальную и позволяете специализированным классам заменять ее. Это почти всегда [1] признак или плохой дизайн (вы можете и должны реализовать свой код, чтобы вам не понадобилась эта идиома).

[1] - «почти всегда» здесь означает, что я не вижу причин для этого, но, возможно, я что-то упускаю :).

4
ответ дан 3 December 2019 в 01:13
поделиться

Я бы точно не стал поклонником этой двухступенчатой ​​конструкции. Есть множество других способов обойти вызов исключения / ошибки в конструкторе. Во-первых, любой достойный компилятор C ++ должен иметь возможность генерировать исключение из конструктора и позволять его надлежащим образом перехватывать.

Другое решение - иметь код внутренней ошибки, аналогичный подходу posix errno . И вызывающий может затем запросить этот код ошибки через вызов члена класса. В IIR Windows есть что-то подобное с функцией GetLastError .

0
ответ дан 3 December 2019 в 01:13
поделиться

Это полезно, когда вам нужно предоставить пользователю (вашего класса) больше контроля над распределением ресурсов/ освобождением. Например, подумайте о классе Socket. Пользователь передает конструктору параметры хоста и порта и может пожелать отложить фактическое «открытие» сокетов (т.е. выделение низкоуровневого объекта SOCKET) на более позднее время. Он также может захотеть закрыть и вновь открыть Розетку по своему желанию. Двухфазное построение (или Lazy Initialization) облегчает это. Такой интерфейс Socket будет выглядеть так:

class Socket
{
public:
    Socket (const std::string& host, int port) : host_(host), port_(port), sock_(NULL) { }
    ~Socket () { close (); }
    void open () throw (NetworkException&) 
    {
         sock_ = new_low_level_socket (host_, port_);
    }
    void close ()
    {
       if (sock_)
       {
          close_low_level_socket (sock_);
          sock_ = NULL;
       }
    }
  // private members
};

// Usage:

ing
main ()
{
    Socket sock ("www.someurl.com", 80);
    sock.open ();
    // do something with sock
    sock.close ();
    // do something else
    sock.open();
    // do something with sock
    return 0; 
    // sock is closed by destructor.
}

BTW, эта идиома не является заменой для предотвращения выбрасываемых исключений из конструктора. Если конструктор завершается ошибкой, создайте исключение. Для получения дополнительной информации см. этот BS FAQ и запись на C++-FAQ-Lite.

5
ответ дан 3 December 2019 в 01:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: