Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, jazzer, Вы писали:
J>>В общем, стандартные прелести двухфазки
U>на самом деле это отличается от двухфазовой инициализации, т.к. к моменту завершения работы конструктора объект полностью инициализирован и находится в консистентном состоянии. готов к использованию.
Это несущественно, так как ничем не отличается от производного класса, который в своем конструкторе делает все те же самые присваивания. А насчет консистентности состояния — несколько странно оставлять консистентность на откуп внешней функции, не находишь? Ведь функция может ничего и не делать, сразу return... Так что консистентность должна быть сразу, и без этой функции.
У меня в проекте похожий подход используется, тоже последним параметром в конструктор базового класса передается std::function) — и делается это просто для того, чтоб прогнать какой-то код (какой- определяется наследником) между конструктором базы и конструктором наследника. Но по сути это все равно двухфазка, просто вторая фаза срабатывает между конструкторами.
Код примерно такой (пример умозрительный, реально там образуется куча подключений, memory-mapped файлов и всего такого прочего, плюс кое-какие данные заполняются в самом Base определенным образом, как нужно наследнику, зовутся всякие веселые инициализаторы, написанные на Питоне, и вот в таком роде все веселье):
struct Base {
string path_;
Base( string app_name, std::function<void()> post_init = Base::default_init )
: path_(private_common_data_path + app_name)
{
post_init();
}
};
struct Derived : Base {
ostream logger_;
ComponentX x_;
Derived(string app_name)
: Base( app_name, [this]{ mkdir(path_); } )
, logger_( path_ + "/log.txt" )
, x_( logger_ )
{}
};
тут у нас есть компонент x_ (один из многих), который использует логгер (он отдается ему в конструктор и должен быть уже полностью рабочим к этому моменту).
Логгер — это член logger_, который открывает файл на запись — но чтобы открыть файл, нам нужно сначала создать директорию, где этот файл будет лежать.
Класс Base ничего ни о каких директориях не знает, он просто генерит и хранит путь.
Получается, директорию надо создать после того, как отработал конструктор Base (потому что нам нужен уже готовый path_), но до того, как начнет работать конструктор Derived, инициализирующий все его компоненты (т.е. тело конструктора Derived отпадает).
Именно это и делает post_init() выше (на самом деле, там после post_init в конструкторе Base еще кое-что зовется, типа проверок консистентности всех сгенеренных/измененных данных и т.д., плюс между Base и Derived есть еще пара классов, так что совсем вынести всё в промежуточного наследника легко не удастся). При этом по умолчанию Base кое-какие действия тоже предпринимает (Base::default_init), но это можно выключить и предоставить свои (как замену или как дополнение — никто не мешает позвать Base::default_init внутри твоего производного post_init).