struct Font {
...
// The foreign constructor.
Font(std::function visit) {
visit(*this);
}
};
class Button {
const Font font = Font([] (Font& font) {
font.weight = Weight::VeryBold;
font.slant = Slang::Oblique;
font.halign = Anchor::Left;
});
};
Единственное что мне не нравится, доступа к protected/private полям класса без сеттеров уже не получить, что делает подобный подход чуть менее читабельным.
Здравствуйте, kaa.python, Вы писали:
KP>Довольно элегантное решение для читабельной, одношаговой инициализации сложных структур данных в C++: http://gerardmeier.com/foreign-constructors-cpp KP>В кратце: KP>
KP>Единственное что мне не нравится, доступа к protected/private полям класса без сеттеров уже не получить, что делает подобный подход чуть менее читабельным.
а чем это лучше обычного
const Font font = [] () {
auto font = Font();
font.weight = Weight::VeryBold;
font.slant = Slang::Oblique;
font.halign = Anchor::Left;
return font;
}();
Здравствуйте, kaa.python, Вы писали:
KP>Довольно элегантное решение для читабельной, одношаговой инициализации сложных структур данных в C++: http://gerardmeier.com/foreign-constructors-cpp KP>В кратце: KP>
KP>Единственное что мне не нравится, доступа к protected/private полям класса без сеттеров уже не получить, что делает подобный подход чуть менее читабельным.
Сделай производный класс. Кстати, в его конструкторе можно обращаться к полям и методам короче и есть доступ к protected.
class Button
{
class CustomFont : public Font
{
public:
CustomFont()
{
weight = Weight::VeryBold ;
slant = Slang::Oblique ;
halign = Anchor::Left ;
}
};
const CustomFont font;
};
Здравствуйте, kaa.python, Вы писали:
KP>Здравствуйте, jazzer, Вы писали:
J>>Ну этому способу сто лет в обед, но главное — это не конструктор же, это двухфазка
KP>В каком месте это двухфазка?
У тебя все члены инициализируются (по умолчанию), а потом начинаются присваивания поверх уже инициализированных членов. Инициализировать члены таким способом не удастся, плюс накладывается дополнительное ограничение насчет поддержки конструирования по умолчанию и последующего изменения (присваиванием или еще как) — то есть константные члены тоже идут лесом.
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, jazzer, Вы писали:
J>>В общем, стандартные прелести двухфазки
U>на самом деле это отличается от двухфазовой инициализации, т.к. к моменту завершения работы конструктора объект полностью инициализирован и находится в консистентном состоянии. готов к использованию.
Это несущественно, так как ничем не отличается от производного класса, который в своем конструкторе делает все те же самые присваивания. А насчет консистентности состояния — несколько странно оставлять консистентность на откуп внешней функции, не находишь? Ведь функция может ничего и не делать, сразу return... Так что консистентность должна быть сразу, и без этой функции.
У меня в проекте похожий подход используется, тоже последним параметром в конструктор базового класса передается std::function) — и делается это просто для того, чтоб прогнать какой-то код (какой- определяется наследником) между конструктором базы и конструктором наследника. Но по сути это все равно двухфазка, просто вторая фаза срабатывает между конструкторами.
Код примерно такой (пример умозрительный, реально там образуется куча подключений, memory-mapped файлов и всего такого прочего, плюс кое-какие данные заполняются в самом Base определенным образом, как нужно наследнику, зовутся всякие веселые инициализаторы, написанные на Питоне, и вот в таком роде все веселье):
тут у нас есть компонент x_ (один из многих), который использует логгер (он отдается ему в конструктор и должен быть уже полностью рабочим к этому моменту).
Логгер — это член logger_, который открывает файл на запись — но чтобы открыть файл, нам нужно сначала создать директорию, где этот файл будет лежать.
Класс Base ничего ни о каких директориях не знает, он просто генерит и хранит путь.
Получается, директорию надо создать после того, как отработал конструктор Base (потому что нам нужен уже готовый path_), но до того, как начнет работать конструктор Derived, инициализирующий все его компоненты (т.е. тело конструктора Derived отпадает).
Именно это и делает post_init() выше (на самом деле, там после post_init в конструкторе Base еще кое-что зовется, типа проверок консистентности всех сгенеренных/измененных данных и т.д., плюс между Base и Derived есть еще пара классов, так что совсем вынести всё в промежуточного наследника легко не удастся). При этом по умолчанию Base кое-какие действия тоже предпринимает (Base::default_init), но это можно выключить и предоставить свои (как замену или как дополнение — никто не мешает позвать Base::default_init внутри твоего производного post_init).
Здравствуйте, kaa.python, Вы писали:
KP>Довольно элегантное решение для читабельной, одношаговой инициализации сложных структур данных в C++: http://gerardmeier.com/foreign-constructors-cpp
Сама идея передачи функции в конструктор интересна. Может быть, где-то ещё пригодится.
Но вот конкретно здесь — есть проблемы.
— тяжеловесная std::function вместо шаблона
— как уже заметил Jack128, вызов может выродиться (у него, правда, ценой копирования, в надежде на NRVO)
Надо подумать, может, и двухфазную инициализацию можно в похожий паттерн всунуть.
Здравствуйте, kaa.python, Вы писали:
KP>Вот что бы мне хотелось здесь, так это получит доступ к приватным полям инициализируемого класса. Но, похоже что, без вариантов тут
Ну так если есть приватные поля, то и способы работы с ними приватные.
Выходов три
class Secret {
private:
int foo_, bar_, buz_, xyz_;
void setup_xyz();
public:
template<class F, class B, class A>
Secret(F do_foo, B do_bar, A do_all) // настроечных функций может быть больше одной ;)
{
foo_ = do_foo(*this); // (1a)
do_bar(bar_); // (1b)
do_all(*this);
}
void setup_buz(); // (2)friend class Xyzzer; // (3)
};
Здравствуйте, kaa.python, Вы писали:
KP>Единственное что мне не нравится, доступа к protected/private полям класса без сеттеров уже не получить, что делает подобный подход чуть менее читабельным.
А еще оно работает только в случае, если мемберы имеют конструкторы по умолчанию.
Ну и кода сильно меньше не получится в любом случае.
Здравствуйте, uzhas, Вы писали:
PM>>Мне больше всего понравился вариант с лямбдой :
U>какие жертвы ради одного const U>оно того не стоит
Как обычно, зависит от задачи. Может потребоваться инициализировать статическую константу или передать временный объект в функцию. В C++03 приходилось создавать рядом вспомогательную функцию, сейчас для этого есть лямбда по месту использования. Синтаксический оверхед, но удобный.
Здравствуйте, kaa.python, Вы писали:
KP>Довольно элегантное решение для читабельной, одношаговой инициализации
Чем паттерн Builder не угодил?
const Font f = FontBuilder().bold(true).strike(false).color(Color.red).build();
Оно, конечно, многошаговое- но так оптимизирующий компилятор разве не вычистит лишние операции?
Здравствуйте, jazzer, Вы писали:
J>В общем, стандартные прелести двухфазки
на самом деле это отличается от двухфазовой инициализации, т.к. к моменту завершения работы конструктора объект полностью инициализирован и находится в консистентном состоянии. готов к использованию.
вдобавок, при таком подходе все равно не получится сделать shared_from_this при инициализации (или что-то уже придумали?), стандартная двухфазка нужна в этом случае
Здравствуйте, Aртёмка, Вы писали:
Aё>const Font f = FontBuilder().bold(true).strike(false).color(Color.red).build();
Aё>Оно, конечно, многошаговое- но так оптимизирующий компилятор разве не вычистит лишние операции?