Я имею как часть присвоения для изучения комплекта разработчика, который использует "двухфазную" конструкцию для классов 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;
}
Почему был бы это "двухфазное" использоваться и что преимущества там? Почему не только инстанцируют объектной инициализации в фактическом конструкторе?
Документ о Двухфазном строительстве.
Идея заключается в том, что вы не можете вернуть значение из конструктора, чтобы указать на неудачу. Единственный способ указать на неудачу конструктора - выбросить исключение. Это не всегда желательно, не в последнюю очередь потому, что безопасность исключений - очень сложная тема.
Поэтому в данном случае конструкция разделяется: конструктор, который не выбрасывается, но и не инициализируется полностью, и функция, которая выполняет инициализацию и может вернуть признак успеха или неудачи без (обязательного) выбрасывания исключений.
Нет никаких веских причин делать это - избегайте. Автор кода, вероятно, просто не знал, что делает.
Приведенный источник крайне причудлив.
Если это 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 является шаблоном, используемым в стандартной библиотеке для таких ресурсов. Обычно вы должны создать объект за один раз и ожидать, что он будет действительным.
Иногда приходится использовать C++ без включенных исключений. В такой ситуации отсутствие возвращаемого конструктором значения усложняет жизнь, поэтому вместо этого у вас есть явная функция конструирования, которую вы можете проверить на успех.
Причиной для этого может быть то, что конструкторы не возвращают никаких значений. Некоторые предпочитают делать Create как функцию, которая должна вызываться после инстанцирования объекта. В случае, если вы не используете исключения, потому что они создают много кода (особенно в мире embedded), невозможно использовать только конструктор, потому что он не дает никаких гарантий (как я уже сказал, он не может возвращать никаких значений).
Другая техника, которую я видел, была примерно такой:
XObject* o = new XObject();
o->IsOk();
Она в основном выполняет конструирование в конструкторе и хранит результат операции в переменной - это не экономит место.
Зачем использовать эту «двухфазную» и какие преимущества от нее?
Эта идиома используется по двум причинам:
Эта идиома использовалась в прошлом до того, как использование исключений было стандартизировано в языке, а также разработчиками библиотек, которые не понимали (или по какой-то причине не хотели использовать) исключения.
Вы по-прежнему найдете его в устаревшем / обратно совместимом коде (например, MFC).
Construction
как (чистую) виртуальную и позволяете специализированным классам заменять ее. Это почти всегда [1] признак или плохой дизайн (вы можете и должны реализовать свой код, чтобы вам не понадобилась эта идиома). [1] - «почти всегда» здесь означает, что я не вижу причин для этого, но, возможно, я что-то упускаю :).
Я бы точно не стал поклонником этой двухступенчатой конструкции. Есть множество других способов обойти вызов исключения / ошибки в конструкторе. Во-первых, любой достойный компилятор C ++ должен иметь возможность генерировать исключение из конструктора и позволять его надлежащим образом перехватывать.
Другое решение - иметь код внутренней ошибки, аналогичный подходу posix errno . И вызывающий может затем запросить этот код ошибки через вызов члена класса. В IIR Windows есть что-то подобное с функцией GetLastError .
Это полезно, когда вам нужно предоставить пользователю (вашего класса) больше контроля над распределением ресурсов/ освобождением. Например, подумайте о классе 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.