Pull to refresh

Comments 48

Забавно было бы посмотреть, а кому реально это пригодится и в каком случае.

Обычно, как вы правильно заметили, процесс создания множества разных объектов одним методом делается либо с помощью фабричного метода (BaseClass* obj = BaseClass::Create(...)), либо с помощью абстрактной фабрики (BaseClass* obj = Fabric::Get(...)->Create(...)). А такое чудо-юдо я даже не знаю, где и применить.

Кстати, техника «конверта» и «письма» — это практически в чистом виде классический плюсовый smart pointer.
Кстати, техника «конверта» и «письма» — это практически в чистом виде классический плюсовый smart pointer.
Не совсем. Здесь конверт и письмо — это один и тот же класс. А умный указатель — это отдельный класс, который вообще ничего не знает о типе объекта, который в нём хранится.
Моё дело познакомить Вас с такой возможностью, а использовать её или нет — решать Вам.

Кстати, «письмо и конверт» — это, можно сказать, особый вид паттерна State.
UFO just landed and posted this here
UFO just landed and posted this here
«…чтобы обеспечить заявленную логику, базовый класс ДОЛЖЕН знать о всех потомках, которые будут от него унаследованы когда-нибудь».

Это плата за то чтобы клиент базового класса о потомках даже не подозревал.
Это плата за то что все конструкторы наследников — приватные. А зачем?
Поясню свою мысль:

В производных классах техник FireSkill, WoodSkill и т.д. конструкторы по умолчанию закрыты, но базовый класс Skill объявлен как friend, что позволяет создавать объекты этих классов только внутри класса Skill.

Зачем такое ограничение? В данном примере объекты-то получатся взаимозаменяемыми, вне зависимости от того, созданы ли они через Skill(int) или непосредственно.

Если это ограничение убрать, и конструкторы сделать открытыми, friend не нужен.
Если открыть конструкторы производных классов, то можно будет создавать объекты классов не через виртуальный конструктор. Если Вас такое поведение устраивает, то Вам вообще нет смысла использовать такую технику.
Фабрика в данном случае не причем. Вирутальный конструктор реализуется посредством виртуальной ф-ии clone(), которая возвращает указатель на реально содержащийся в классе тип, а никак не посредством фабрики.
Фабрика вообще-то самый популярный способ реализации «виртуального конструктора». А метод clone (оно же паттерн прототип) это по сути некий аналог аналог «виртуального» конструктора копии, более узкий случай.
Не хочу с Вами спорить, но фабрика и виртуальный консруктор немного разные вещи, хотя грань и не велика и многие, по ошибке называют Виртуальный конструктор Фабрикой.
Три ссылки, которые рассматривают виртуальный конструктор: раз, два, три
Спорить смысла нету — это вопрос опредения понятия «виртуальный конструктор», которое отсутствует в С++. Фабрики ближе к понятию «метакласс», но по большому счету оба паттерна по-своему подходят под это название.
Вообщето весь прогркссивный мир пдля такиз цеоей пользуется фабриками. То, что вы тут накуралесили — ЕРЕСЬ.
Во-первых, это не накуралесил, а Coplien.

А во-вторых, я не заставляю Вас пользоваться описанной методикой. Если она Вам не подходит, то это еще не означает, что она бесполезна. У всех паттернов есть свои достоинства и недостатки.
Спасибо за то что поделились необычной техникой.

Я ещё не придумал как это всё можно использовать, но зато в голову пришла безумная мысль. Скажем, в конверте может храниться не один указатель, а несколько. Например, в случае водно-земельного заклинания конверт мог бы хранить указатели на водное и земляное заклинания. В идеале, вызов skills[i]->Attack(); должен вызывать виртуальные функции-члены для всех внутренних указателей. К сожалению, так красиво сделать не удастся, зато вот такой вызов реализовать вполне можно: skills[i]->Make(&Skill::Attack);

Конечно, приведённый пример скорее из серии эквилибристики, чем реально полезных вещей. Хотя кто знает… )
Да, примерно об этом я и говорил. Вот только описывать базовый вызов для каждой функции очень муторно и потенциальный источник ошибки. Есть вариант сделать это банально через пропроцессор, но я бы предпочёл чуть более сложный в вызове универсальный вариант:
void Skill::Make (void (Skill::*foo)())
{
    for (size_t i = 0; i < mLetters.size(); ++i)
        (mLetters[i]->*foo)();
}

...skills[i]->Make(&Skill::Foo);

Да, примерно это я и подразумевал под «порефакторить» ;)
Классно! Теперь бы придумать где такое можно применить :)
Можно, к примеру, сделать лексический анализатор, который будет использовать виртуальный конструктор для создания определенного вида лексем на основе какой-то входной инфы…
а в чем плюсы то такого полета фантазии?
по сравнению с фабриками мы лишаемся наглядности.
параметр, который на самом деле представляет тип — это ахтунг и фундамент для долгих дебагов, ибо лишает нас помощи компилятора и переносит контроль типов на наши плечи. одно изменение — и компилятор не сообщит обо всех ошибках. тока Control+F. А если кто дефайнул констант по-своему…
Базовый класс, который знает наследников — это пять… Игра явно не стоит свеч.
я бы отнёс подобный код к запретным техникам (kinjutsu, 禁術)… не изящный он какой-то
О киндзюцу тоже надо иметь представление ;)
спасибо, способ оригинальный и статья полезная, лучше кучи других в которых описываются элементарные вещи, которые легко находятся в любом учебнике.
Достаточно странный метод, если честно.
Вся история с виртуальными функциями придумана чтобы избежать switch...case по типам. А тут он в явном виде присутствует :(
В книге GoF при реализации параметрического Фабричного Метода тоже используется набор if-ов…
Вообще само по себе определение «виртуальный конструктор» не совсем корректно — виртуальный метод выбирается зависимо от реальной интстанции объекта стоящего за указателем на базовый класс. К конструктору то применить вообще-то нельзя :)

С другой стороны, если пойти в торону метаклассов, то конструктор есть методом метакласса, и тогда надо делать объект Type с виртуальным методом CreateInstance. Получим абстрактную фабрику.

В этом примере действительно смущает оператор switch, такой код в принципе несложно переписать с фабриками и он будет выглядеть более изящно. Для выбора по элементу энама можно сделать например массив фабрик.

Впрочем это все философия на тему что такое «виртуальный конструктор» вообще. Термин не определен языком С++, так что спорсить особо некуда :)
В этом случае я бы предпочел другую формулировку виртуального метода: виртуальный метод — это метод, нужная версия реализации которого выбирается на этапе выполнения, а не на этапе компиляции.
А кто-нибудь встречал Коплиена в электронном варианте? Я не нашёл.
Jim Coplien значится в рецензентах и «благодарностях» к многим книгам других известных авторов, даже у Страуструпа по-моему… Так что, возможно, Вы его уже читали :)
Нет я уверен, что читал Страуструпа, Мейерса, Александреску, Эккеля, Гамму, Хелма, Влиссидеса и других, но Коплиена среди них не было ;). Хотелось бы найти всё-таки эти книги — Advanced C++ и Multiparadigm Design for C++. Гугль настойчиво предлагает купить :(
Я не буду кричать, что это ересь или бред. Просто потому, что это ни разу не так.
Необычно — да. Я о такой возможности не задумывался. Может быть, где-то пригодится.
В любом случает, спасибо автору статьи за труд :)
не совсем понял вот это:
>Виртуальный конструктор должен быть определен ниже всех производных классов,
>чтобы не пришлось париться с опережающими объявлениями (forward declarations), так
>как внутри него создаются объекты производных классов.

это вы вообще, или про конкретную вашу реализацию?
Это я конкретно про текущую реализацию.
А если мне нужно будет через пол года добавить параметризованный конструктор в наследника?
Если есть шанс того, что Вы будете вносить подобные изменения, их нужно предусмотреть на этапе проектирования, а значит — подобный «мегахак» не для Вас.
Всего не предусмотришь. Я не сторонник мегапроектирования всей системы сразу и навеки. Лучше заранее избегать таких вот «мегахаков» и использовать гибкие решения, чтобы потом не пришлось переписывать всю систему.
Я не говорил о мегапроектировании, но лучше все-таки хотя бы локализовать область возможных изменений.

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

Поэтому чтобы не совращать неокрепшие умы, стоит добавлять к этому паттерну приставку «анти».
Я не против, ибо в принципе я и не приписывал такой подход к паттернам.
Все думал, думал чтоже мне напоминает данный паттерн. И вот вспомнил Curiously recurring template pattern (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). И как я удвился когда увидел, что автор тоже Джим Коплиен. Хочу сказать, что CRTP действительно интересный паттерн и когда-то сам его использовал. Слышал что в Adobe его пользовали и даже вроде в QT. А в статье видимо его динамический аналог.

И всё-же это ересь и бред. Да, так написать можно, но за такой код надо убивать, а потом увольнять. Он противоречит основным парадигмам ООП: получается, что базовый класс обязан знать всё о своих наследниках — сколько их, как они называются, сколько в них функций, с какими параметрами и т.д. И каждый раз при добавлении нового класса\функции нужно будет переписывать вообще всё, включая базовый класс и его конструктор.

Этот пример красиво и смачно плюёт на всё ООП в целом (и особо ярко на наследование с полиморфизмом в частности), забивает микроскопом гвозди и единственная от него польза — объяснить почему нужно пользоваться паттерном «фабрик» и не изобретать таких вот велосипедов-монстров.
Ересь и бред — это сначала убивать, а потом увольнять ;)

А вообще, мои друзья из компании, название которой нельзя называть, предложили реализовать то же самое через шаблоны при условии, что нет необходимости задавать тип во время выполнения. Там все ваши парадигмы соблюдены.
И тогда, вероятнее всего, мы придем к одному из сто лет известных паттернов (либо к «вропперу», либо к «умному указателю», либо к той же «фабрике»). И тогда и правда все будет верно и красиво. Скорее всего, приведенный Вами вариант — это один из шагов эволюции на пути к изобретению этих паттернов.
Sign up to leave a comment.

Articles