Как стать автором
Обновить

Комментарии 61

Зачем использовать макросы в плюсовом коде?
Макросы я использовал, начиная с первой статьи. В принципе, можно обойтись и без них. Однако, в данном случае макросы являются чем-то вроде декларации и являются как бы частью интерфейса, дополняют класс в описательном стиле. Я не склонен думать, что макросы — это зло. Просто их надо правильно использовать.

Взять, например, макрос BIND_TO_SELF_SINGLE. Он говорит о том, что синглтон связывается сам с собой. Здесь мне не нужно знать, как этот макрос устроен. Мне нужно знать, что в результате получится. В последствии я могу заменить реализацию макроса (если потребуется), а все пользователи даже и не заметят этого. Таким образом, макросы выступают в роли интерфейса, как это ни странно.
Макросы «очень даже добро» когда они повышают читабельность кода, как например вот тут:
#define PROTO_IFACE(D_iface, D_an) \
template<> void anFill<D_iface>(An<D_iface>& D_an)


Как верно сказал gridem: Однако, в данном случае макросы являются чем-то вроде декларации и являются как бы частью интерфейса, дополняют класс в описательном стиле.
т.е. скрывают реализацию там где она явно не нужна.
oops: т.е. скрывают реализацию там, где её знать не обязательно.
и крайне понижают возможность корректной отладки такого чудо-макро-кода… что мешает заменить макрос inline-функцией?
И как же определение темплейта заменить инлайн-функцией?
параметризованной инлайн-функцией…
Хорошо, на примере PROTO_IFACE, что там куда заменять инлайн-функциями?
Темплейты вообще-то по определению инлайн. Но не всегда хочется писать громоздкую непонятную конструкцию с угловыми скобками и дублированием типов, typedef в этом случае не поможет, а макросы вполне даже.
Конечно, C-style макросы это тихий ужас и привет отладке, но в си++ другого не дано (например, доступа к AST).
мой комментарий относился скорее не конкретно к приведенному примеру, а к комментариям относительно того, что макросы повышают читабельность кода, являются как бы частью интерфейса и скрывают реализацию там где она явно не нужна…

относительно вашего смелого заявления, что «темплейты вообще-то по определению инлайн», хочу Вас огорчить, это не всегда так…

по поводу громоздкой непонятной конструкции с угловыми скобками и дублированием типов; Вы правда считаете, что сокрытие такой конструкции за волшебным макросом повысит читабельность и понимание кода? от дублирования длинных шаблонных типов хорошо помогает typedef, все остальное должно быть явно написано без каких-либо игр в прятки с макросами…

относительно «в си++ другого не дано» я вообще не понял… еще раз в с++ есть все необходимые языковые средства для замены макросов…

на примере PROTO_IFACE нужно оставить явную специализацию шаблона, а не городить макро-огород, часто используемые типа а ля An<D_iface> заменить коротким синонимом при помощи typedef…
Да что же вы так любите недоговаривать? Когда темлейты не инлайн? Естественно, под инлайн мы понимаем опцию линковки, определяемую ключевым словом inline, а не то, что код функций будет заинлайнен компилятором, так ведь?

Да, я считаю, что правильно используемые макросы упрощают понимание и написание кода (но усложняют отладку). Интерфейс верхнего уровня — это «что делать», а не «как делать», и простой синтаксис, поэтому лично я бы предпочёл написать PROTO_IFACE а не специализировать шаблоны.

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

Заменить An<D_iface> typedef'ом не сильно поможет, т.к. останется ещё много вещей, которые придётся продублировать. И опять же, это будут детали имлементации, «как делать», а не «что делать». Вот если бы можно было заменить макросы из статьи на что-нибудь вида BindImpl<IDatabase, MyDatabase>, я был бы согласен, но с декларациями это не работает.

Вообще, вы писали шаблонный код, использующий другие шаблонные классы, и т.п., например биндинги как в статье? Видели исходники boost? И после этого серьёзно считаете, что мета-программирование в си++ всегда лучше, чем макросы? Я считаю, что для полноценного мета-программирования нужен полноценный макро-язык, иначе выходит нечитаемый ужас как в практически любой шаблонной магии си++.
А что же Вы так любите задавать глупые вопросы? :) Когда темлейты не инлайн может зависеть от многих факторов, начиная от реализации конкретного компилятора, его настроек и заканчивая кодом конкрентного темплейта… и я не пойму с чего Вы вдруг приплели сюда линковку, если речь пока идет о препроцессинге и компиляции?

То что Вы считаете, что правильно используемые макросы упрощают понимание и написание код я уже понял и продолжать спорить с Вами смысла не вижу… Так же понятно, что Вы по какой-то причине заочно считаете себя умнее других, в том числе и Стауструпа, и Саттера, и Александреску и прочих Джосаттисов… ну да это со временем проходит… :) Сойдемся на том, что Вы выбрали свой путь. Так же добавлю, что если в вашем коде так много, в частности, специализаций шаблонов, что Вам приходится как-то автоматизировать их набор, то скорее всего стоит задуматься о корректности используемого подхода для реализации задачи…

Вы не поверите, но не только видел исходники stl, boost, loki, mfc, atl, wtl, но еще и написал не мало кода с их использованием, и еще не меньше дебажил чужого кода используещего эти самые библиотеки, а вот судя по Вам, раз уж мы перешли на личности, не хватает опыта участия в крупных промышленных решениях, которые разрабатываются несколько лет, несколькими поколениями разработчиков… так вот отлаживаться и искать дефекты в коде напичканном макросами такого рода проектов занятие скажем так на любителя… И да, вот именно после этого, я категорически считаю, что мета-программирование в си++ всегда лучше, чем макросы.
Ого, я вообще-то и не пытался переходить на личности, просто спросил, из практики вы делаете выводы или просто так, сорри если чем обидел.

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

Вы, видимо, под инлайном подразумеваете то, что код функции будет заинлайнен. Так в этом случае для меня нет никаких откровений, ясно дело всё зависит от компилятора, может и не-inline заинлайниться, а может и вообще link-time «настоящая» функция из другого юнита. Зачем об этом вообще говорить в этом обсуждении?

По-моему, на личности как раз вы переходите. Я-то как раз себя не выставляю умнее других, а пытаюсь обсуждать конкретные вопросы. Классики вроде Страуструпа редко утверждают что-нибудь в духе «макросы не нужны», а говорят о конкретных ситуациях.

В том-то и дело, что макросами не должно быть «напичкано», но это не значит что их вообще нельзя применять там, где уместно. Ещё раз, я бы предпочёл что-то статически типизированное макросам, но не всегда это возможно, приходится жить с тем, что есть.
Да нет проблем, на обиженных воду возят… :) меня просто смутил ваш напор…

Я честно говоря не совсем понял что под inline подразумеваете Вы, можно просто пример небольшой?

Относительно переходя на личности, прошу прощения если не так Вас понял, видимо это у Вас просто манера общения такая… а классики вроде Страуструпа и не только, как раз прямым текстом и говорят, что макросы это зло.

В том-то и дело, что мест таких при программировании на с++ в стиле с++ нет, так же как и нет места в с++ «голым» указателям например…
Ну есть такое, я своё мнение всегда отстаиваю достаточно прямо :) Просто пока что так и не увидел серьёзных аргументов против. Вообще-то я тоже против макросов в целом, но считаю что иногда (довольно редко) с ними лучше чем без них. Макросы — зло, но есть и другие «злы», например копи-паст, и имхо иногда первое оказывается меньшим злом…

Пример с инлайном:
// test1.h
void f() {}

// test2.h
inline void f() {}

// test3.h
template<class T>
void f() {}


Теперь, если мы эти хидеры инклюдим из двух .cpp, то в первом случае естественно получаем ошибку линковки из-за двойного определения. Во втором — этого не будет, т.к. линковка статическая (аналогично static void f() {}, но static неприменим к членам классов, и там остаётся только inline). В третьем варианте тоже проблем не будет, т.к. по стандарту темплейты не приводят к множественному определению, то есть получается тот же inline (или static его называть?..). Вообще, в си++ устроили какую-то мешанину со static, inline и компанией со своими стараниями не добавлять ключевиков, теперь чёрт ногу сломит разобраться что означает что…

Пример с возвращением указателей из функций, кстати, знаковый — вроде все согласны, что в си++ это нехорошо, но при этом не помню чтобы я видел в книжках чтобы те же самые фабрики возвращали не голые указатели. То есть, вроде как нехорошо, но всем пофиг :) Или можете подсказать литературу где это не так?
очень странный пример честно говоря, так как во-первых все хидеры в с/с++ необходимо защищать от повторного включения соответствующими методами, тогда у Вас не будет проблем и в первом варианте… во-вторых ключевое слово static в данном случае говорит компилятору, что область видимости переменной ограничена модулем, в котором она объявлена, насколько я помню… термин статическая линковка первый раз слышу, но понятно о чем Вы, есть понятия внутренней и внешней линковки, которые завязаны как раз на область видимости переменной, тут Вы правы, static в данном примере используется для внутренней линковки… ну и в 3х, первый раз вижу чтобы для такого поведения использовали ключевое слово inline, так как оно предназначено для указания того, что вы хотите чтобы тело функции было вставлено в код вместо ее вызова, собственно из-за этого у нас и возникла легкая неразбериха…

что происходит в вашем примере:
1 — вы все верно описали, но тут проблема защиты от повтороного включения, которой нет
2 — линковщик не ругается по причине того, что он думает, что Вы хотите использовать код функции вместо ее имени, соотвественно проблем с дублированием имен нет… для поведения, которое Вы хотели необходимо использовать ключевое слово static…
3 — с темплейтами ситуация несколько похоже на предыдущую, но линковка будет внешняя

так что получается, что у Вас небольшая неразбериха, а не в с++ :)
Там дело не в повторном включении, а во включении из разных юнитов (.cpp), так что include guards не помогут — проблема именно при линковке.

«статическая линковка» это я от балды так называю (видимо, это внутренняя линковка как вы говорите). Как я писал выше, ключевое слово static тут сработает, но не сработает для членов классов, например:

// .h
class X
{
void f();
}

inline void X::f() {...} // cannot be "static"


Получается,
1 — проблема именно во внутренней/внешней линковке, а не в защите от повторного включения
2 — линковщик ругается, т.к. из двух юнитов (объектных файлов) экспортирована функция с одной сигнатурой
3 — у темплейтов не бывает настоящей внешней линковки, шаблонную функцию снаружи дёрнуть нельзя. Кроме того, компилятор вообще не обязан экспортировать шаблонную функцию. В случае если он всё же это сделает, то множественные определения будут схлопнуты в одно. Если не вдаваться в эти детали, то можно сказать, что темплейты фактически не экспортируются (что и есть моё изначальное утверждение)

У меня-то как раз «разбериха» ;)
1 — все верно, дело во включении из разных юнитов, только кто в здравом уме и для чего это будет делать, если заранее известно, что такой код не «соберется» из-за повтороного определения из хидера без защиты от повторного включения???

2 — я же Вам написал, что inline != static, это разные ключевые слова с разным поведением, мало того что Вы используете их без понимая того для чего они нужны, так еще пытаетесь с умным видом доказать, что все ок, тем самым возможно вводя в заблуждение людей, которые читают эти комментарии… и с чего Вы вдруг решили, что static нельзя использовать для членов и методов класса???

3 — с шаблонами многое зависит от конкретного компилятора, его настроек и конкретной ситуации в коде… так что делать предположения тут дело неблагодарное… и причем здесь экспорт функций???

подучите все-таки матчасть, перед тем как вводить окружающих в заблуждение свои праведным напором, а то некрасиво как-то получается :)
1 — Да причём здесь защита от повторного включения? Речь ведь об include guards? И как же они помогут если проблема на этапе линковки?

2 — я знаю, что inline != static, и знаю для чего они нужны. Что вы опять кричите не разобравшись? С чего вы решили, что static можно использовать для членов и методов класса? Вы же понимаете, что у ключевика static несколько смыслов, и когда я говорю что его нельзя использовать для членов класса это означает не
class X
{
static void f();
};


а совсем другое:

class X
{
void f();
};

static void X::f() {} // compilation error !
inline void X::f() {} // OK


Я как бы понимаю, что static void f() {} и inline void X::f() {} это не совсем одно и то же, но практически и то и другое может использоваться для внутренней линковки.

Если вы считаете, что inline void f() {} обязательно означает, что код функции будет заинлайнен, то извините, но это вам нужно учить матчасть. На практике, различие между inline и static в этом контексте будет очень маленькое — и в том, и в другом случае функция может быть как заинлайнена, так и нет, как решит компилятор.

3 — экспорт естественно в смысле экспорта из юнита для внешней линковки, а не в смысле экспорта из dll, неужели это не очевидно?

По-моему, это вы себя считаете умнее других, и не пытаетесь понять то, что вам говорит собеседник. Честно говоря, дискуссию продолжать особого желания нет…
да епрст… Вы научитесь для начала корректно объяснять свои мысли при помощи устоявшейся терминологии, а не придумывать на ходу свои термины подкрепляя их не совсем корретными примерами…

«inline void f() {} обязательно означает, что код функции будет заинлайнен» я этого нигде не писал, будет заинлайнен код помеченный как inline решает компилятор, но это не значит что нужно использовать inline для внутренней линковки, когда для этого есть один из вариантов static… хотя я сейчас краем глаза глянул описание inline в стандарте и судя по описанию он действительно может использоваться в таком контексте, но мне непонятно зачем это делать если есть специальное средство…

относительно того, что static не сработает для методов класса… откуда Вы взяли такой пример?

class X
{
void f();
};

static void X::f() {} // compilation error!
inline void X::f() {} // OK

для решения какой проблемы необходимо писать такой код?

относительно экспорта очевидно, но после непоняток с inline я решил удостовериться, не сочтите это за проявление высокомерия с моей стороны…
> для решения какой проблемы необходимо писать такой код?

Так делают когда не хотят засорять определение класса инлайн-реализациями, но при этом хотят оставить функцию в хидере, либо просто не хотят создавать ещё один .cpp из-за одной мелкой функции, проще её сделать inline (в смысле внутренней линковки, естественно).

Заметьте, в стандарте написано, что реализации функций внутри класса по определению inline. Но этот inline не означает, что код функции будет заинлайнен. То есть фактический смысл — это внутренняя линковка. Со static'ом вообще кранты, у него минимум 3 разных семантики. Ну и для полного счастья ещё добавили безымянные области видимости, которые тоже позволяют сделать внутреннюю линковку. И вы мне хотите сказать, что всё чисто и очевидно? :)

Я был бы очень рад где-нибудь прочитать чёткую терминологию, но у меня такое ощущение что такого места просто нет, и со всеми этими перегрузками ключевиков вроде static все путаются :\ Я смотрел в стандарт, сильно яснее не стало…
теперь понятно… как говрится, век живи век учись, а помрешь все равно дураком… :) у меня просто никогда не возникало потребности так делать, так как всягда оставлял по-минимуму в хидере, только интерфейс, вся реализация, даже простые геттеры и сеттеры, в сипипишник, а то сегодня это простой геттер, а завтра не очень простой… да и члены класса так же полезно за pimpl спрятать… ну это уже у каждого свое кунг-фу…

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

за терминологией мне кажется в стандарт или к страуструпу, как первоисточникам… хотя на мой взгляд оба эти манускрипта достаточно сложно перевариваемые, лучше почитать мейерса, саттера и александреску, у них все сжато и по делу, без лишней воды и с терминологией вроде все в порядке…
Да уж, что-что, а соскучиться с++ точно не даст…
Действительно, нужно перечитать снова классиков, каждый раз замечаю что на что-то в прошлый раз не обратил внимания, в этот раз это может быть терминология :)
Вы бы другу другу плюсовали комменты, что ли.
Зачем?
в книжках просто стараются не нагружать читателя лишней информацией в примерах, по это причине могу использоваться примитивные конструкции… так же как например всегда опускается обработка ошибок для простоты примера кода… часто об это указывают в комментариях…

ну а по литературе тут вроде все давно все устаялось и не раз уже перечислялись все значимые книжки, основные наверно следующие:

www.ozon.ru/context/detail/id/2381848/
www.ozon.ru/context/detail/id/1273200/
www.ozon.ru/context/detail/id/2342923/
www.ozon.ru/context/detail/id/1224782/
www.ozon.ru/context/detail/id/2610625/
www.ozon.ru/context/detail/id/2623946/
www.ozon.ru/context/detail/id/3960662/
www.ozon.ru/context/detail/id/85559/
www.ozon.ru/context/detail/id/2576269/
www.ozon.ru/context/detail/id/4751845/
Спасибо, конечно, за список книжек, но я в общем-то спрашивал не просто про книжки по плюсам, а вполне конкретный вопрос про фабрики возвращающие не голые указатели… Мне кажется, возвращение обёртки вместо умного указателя не сильно усложнит примеры, но почему-то я такого не помню из книжек.
относительно фабрик, тут вообще книжки по плюсам не нужны, достаточно прочитать одну — паттерны проектирования от банды 4х, там кста помимо примеров на плюсах, есть примеры и на смолталк, если вдруг так смущают голые указатели в примерах…

Вам так кажется потаму что Вы знакомы с этой концепцией, а представьте человека, которые еще с ней не знаком, но решил почитать про паттерны, для него это будет лишняя на тот момент информации, которая может затруднить восприятие основной темы…
@datacompboy:
habrahabr.ru/blogs/cpp/118368/
«Стоит сразу оговориться, что такой подход мне нигде не встречался. Так что будем своего рода первооткрывателями.»
Хочется отписать в комментах: поздравляю, вы изобрели сборщик мусора.
Имелся ввиду подход, где не используется new и delete в явном виде, при этом объекты создаются в куче. Даже, например, в Java этого не видел, хотя там есть сборщик мусора, но везде и всюду разбросаны new .
Конечно, хочется выразить благодарность человеку, отобравшему кусочек нажитого непосильным трудом…

Касательно макросов, поясню свою позицию:
Макросы — это препроцессор, а не средство самого языка, причем доставшееся еще из С. Думаю это понятно.
Не всегда макросы могут помочь сделать код более читабельным и понятным. Приходится помнить, что в данной вот точке кода, определенное слово будет подменено препроцессором на некую штуку, которая объявлена где-то там. Особую радость доставляет разбираться в таком коде, особенно когда макрос включает в себя другой макрос, а то еще один. Очень весело, бывает, отлаживать такой код в дебаггере. Когда видишь в тексте одно, а для дебаггера, в бинарном файле, там нечто другое. Приходится скакать по макросам, инклудам, разбираться, что же там на самом деле происходит.
Иногда макросы могут завуалировать собой некоторые проблемы, которые могут вылезти потом в рантайме.
Очень плохой практикой считаю задание всяких вычислительных значений посредством макросов, т.к. данные вычисления будут производится Каждый раз, в Каждой строчке кода, где используется этот макрос! В то время, как значения заданные посредством той же константы будут вычислены компилятором на этапе сборки программы и в дальнейшем будет использоваться уже вычисленное значение.
Поэтому в свете сказанного я предпочитаю использовать inline-функции, константы, тайпдефы и прочие средства Языка, а не Препроцессора.
Разумеется все вышесказанное мое глубокое имхо, основанное на личном опыте.
Просто стало интересно, зачем юзать макросы в C++ коде, в то время как космические корабли бороздят просторы большого театра в 2011 году.
спешу Вас обрадовать и заверить, что ваше глубокое имхо это нормальная практика профессионального программирования на с++ :)
Статья очень интересная, но не слишком ли усложняется singleton?
Для подавляющего большинства задач хватит и реализации RSDN (последний шаблонный вариант).
Я, конечно, извиняюсь, но по-моему, так писать вообще не стоит. Зачем мне помнить, что где-то там надо освободить синглтон? К тому же такой подход содержит абсолютно все недостатки, которые указаны в первой статье.
template<typename T>
T& single()
{
    static T t;
    return t;
}


Если мне не изменяет память, так делать нельзя — стандарт C++ не гарантирует, что static объект в функции переживет вызов функции. То есть конкретные реализации C++ так делают, но ничто не защищает нас от повторения в будующем истории с memcpy, если какая-то реализация будет разрушать объект при выходе из функции и восстанавливать его при повторном вызове. По стандарту — имеют полное право так делать. ИМХО, закладываться на не описанную в стандарте реализацию компиляторов — не лучшая идея.

Или память мне все-таки изменяет?
> Если мне не изменяет память, так делать нельзя — стандарт C++ не гарантирует, что static объект в функции переживет вызов функции.

Изменяет, потому что гарантирует. Этот приём иногда называют синглтоном Майерса. У этого приёма есть преимущества и недостатки.
То есть 3.6.3(1), «If a function contains a local object of static storage duration that has been destroyed and the function is called during the destruction of an object with static storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed local object.» вас не настораживает?

Скажем так — я бы постерегся трактовать 3.7.1(1) и 12.4(10) литерально при наличии 3.6.3(1) и 3.7.1(3) и, особенно, 3.7.1(2).

Фишка, ИМХО, заключается в том, что стандарт разделяет определения «static storage duration» и «static local variable». Причем второе везде идет как исключение (3.6.3, 3.7.1).

Хотя, в целом, соглашусь с вами — память меня подвела, ничего фатально противоречащего стандарту в таком использовании нету.

Можно переходить к многопоточности? :)
Переходить к многопоточности еще рано. Про это будет через одну статью.
НЛО прилетело и опубликовало эту надпись здесь
Это только часть того, что бы я хотел описать.
Неверно толкуете стандарт. Этот пункт говорит о том, что поведение не определено в следующем случае:

struct A {};

void foo() { static A a; };

struct B {
  ~B() { foo(); };
};

void main() {

  static B b;
  foo();

};
Александреску отлично описал 3 разных способа создания синглтона для разных случаев, просто и понятно, к чему у вас такие сложности?
Этот же комментарий был к первой статье. Приведу его еще раз:

«Речь в ней идет не о реализации, а об использовании… Можно взять реализацию из Александреску. Книжка его очень умная и толковая. Но вся статья написана о том, как использовать и убрать недостатки, присущие этому паттерну.»
Вы знаете, мне кажется, вы просто не умеете готовить Loki:

#include "stdafx.h"
 
#include <iostream>
#include <Singleton.h>
 
template< class T >
struct An
{
    typedef Loki::SingletonHolder< T > TSingleton;
 
    An(){ TSingleton::Instance(); }
 
    T &operator*(){ return TSingleton::Instance(); }
    T *operator->(){ return &TSingleton::Instance(); }
};
 
struct A
{
    A(){ std::cout << "A" << std::endl; a = 1; }
    ~A(){ std::cout << "~A" << std::endl; a = -1; }
 
    int a;
};
 
struct B
{
    An< A > pa;
 
    B(){ std::cout << "B" << std::endl; }
    ~B(){ std::cout << "~B" << std::endl; out(); }
 
    void out(){ std::cout << pa-><< std::endl; }
};
 
int _tmain( int, _TCHAR*[] )
{
    An< B > pb;
    pb->out();
 
    return 0;
}
 
А ведь там еще есть явный контроль времени жизни, поддержка многопоточности, феникс синглтоны, проверки для исключения использования разрушенных объектов и т.д.
Вы можете использовать вместо синглтона Майерса синглтон Александреску без каких-либо проблем. Я использовал наиболее простой способ для демонстрации идеи. Про многопоточность будет в другой статье.
Во-первых, предложенный класс An не может иметь синглтоном наследника, поэтому, как частный случай, T не может быть абстрактным. Во-вторых, An может использовать любую реализацию, которая заливается непосредственно в класс, а не обязательно синглтон (см. первую статью). В-третьих, нет отложенности вычислений, что вызовет проблемы с зависимостями.
я вас понял, но все-таки попробуйте вынести ваши create и т.п. в стратегии, как-то так:

#include "stdafx.h"
 
#include <iostream>
#include <memory>
 
// Loki::CreateUsingNew with abstract classes support.
template< class T, class I = T >
struct CreateUsingNew
{
    static I* Create(){ return new T; }        
    static void Destroy( I* p ){ delete static_cast< T * >( p ); }
};
 
// Use CreateUsingNew< T > as the default creation strategy.
template< class T >
struct SelectCreationStrategy
    : CreateUsingNew< T >
{
};
 
template< class T, template< class > class TCreationStrategy = SelectCreationStrategy >
class An
{
    std::shared_ptr< T > _guard;
 
    T &get()
    {
        static std::shared_ptr< T > p( TCreationStrategy< T >::Create()&TCreationStrategy< T >::Destroy );
 
        _guard = p;
 
        return *p;
    }
 
public:
 
    T &operator*(){ return get(); }
    T *operator->(){ return &get(); }
};
 
// Note: usage of this singleton is the same as in the previous variate, no macro, etc.
struct A
{
    A(){ std::cout << "A" << std::endl; a = 1; }
    ~A(){ std::cout << "~A" << std::endl; a = -1; }
 
    int a;
};
 
// Abstract
struct B 
{
    B(){ std::cout << "B" << std::endl; }
    ~B(){ std::cout << "~B" << std::endl; }
 
    virtual void out() = 0;
};
 
// Impl
struct C : B
{
    An< A > pa;
 
    C(){ std::cout << "C" << std::endl; }
    ~C(){ std::cout << "~C" << std::endl; out(); }
 
    virtual void out(){ std::cout << pa-><< std::endl; }
};
 
// Overridden creation strategy for B.
// Note: you can use even custom allocators here.
template<>
struct SelectCreationStrategy< B >
    : CreateUsingNew< C, B >
{
};
 
int _tmain( int, _TCHAR*[] )
{
    An< B > pb;
    pb->out();
 
    return 0;
}
 


Имхо читабельнее, плюс есть возможность не-new выделения памяти, плюс поддержка семантики глубокого копирования на уровне стратегий (нужно еще Clone добавить в стратегию естественно)… хотя я очень слабо понимаю, зачем синглетонам может понадобиться глубокое копирование)
Можно ли залить любую реализацию в класс An руками, например класс D, который наследует B (пункт «во-вторых»)?
template<>
struct SelectCreationStrategy< D >
     : CreateUsingNew< D, B >
{
};

Если нужно, допустим, какой-нибудь параметр еще передать, можно так:
template<>
struct SelectCreationStrategy< D >
{
     static B* Create(){ return new D( /* something */ ); }        
     static void Destroy( B* p ){ delete static_cast< D * >( p ); }
};

Естественно, все это должно быть в h файле
Я хотел следующее: по умолчанию заливается C неявно, но иногда я хочу явно заливать D. Такое возможно? Далее, хотелось бы, чтобы решение о том, что заливать неявно, находилось в cpp файле.
что-то я не понимаю… это же синглетон, как понять «иногда»? Для всех тех ситуаций, когда вместо C должно быть D должна быть определена соответствующая специализация SelectCreationStrategy до того как будет попытка использовать синглетон соответствующего типа («до» имеется в виду по коду, а не по времени).

ну в вашем коде ведь так же…

можно еще писать An< C, TCreationStrategy_1 > и An< C, TCreationStrategy_2 > с явным указанием стратегии, только это уже будет 2 разных синглетона с одинаковым интерфейсом.

В общем, можно подумать, только опишите реальную ситуацию
Написал Update в конце статьи. Если есть еще вопросы, спрашивайте. Тема оказалась непростой, хотя мне поначалу казалось, что все просто.
К чему такие сложности? 95% юзкейсов описывается первым шаблоном. А если не описываются, то надо еще архитектуру пересмотреть. А то бывает, что один синглтон от другого зависит и начинаются чудеса.
Отложенность вычислений для этого синглтона позволяет правильно разрулить зависимости при инициализации. Контроль времени жизни разруливает уничтожение.
Зависимости при инициализации надо стараться избегать а не разруливать, тогда волосы целее на голове будут.
Все зависит от сложности задачи. Бывают настолько сложные системы, что просто так разрулить не получается. Предложенный подход эту проблему решает на корню. Это как с shared_ptr: можно его и не использовать и самому следить за памятью. Но зачем?
И вообще более того, лучше даже сделать namespace с нужными методами, а все общие поля хранить в чем-нибудь типа

static scoped_ptr ptr;

Тогда никакая зараза не сможет удалить синглтон и можно вполне нормально рулить его временем жизни. Но вообще делать что-то в деструкторе синглтона — это ходить по граблям.
scoped_ptr нельзя копировать, поэтому нельзя контролировать время жизни подобно тому, как описано в статье. По поводу грабель вы правильно написали, но предложенный подход эти грабли убирает.
Так можно любой pointer сделать внутри реализации, зато интерфейс будет чистым как слеза младенца.
главное не забудьте про циклические ссылки
Будем использовать умный указатель из стандартной библиотеки std::shared_ptr заголовочного файла memory. Стоит отметить, что такой класс доступен для современных компиляторов, которые поддерживают стандарт C++0x. Для тех, кто использует старый компилятор, можно использовать boost::shared_ptr.


Да вроде не обязательно использовать новейшие компиляторы или буст:
sveolon@sveolon-laptop ~/build/tmp $ cat ./main.cpp
#include <tr1/memory>
#include int main()
{
std::tr1::shared_ptr p (new int(3));
std::cout << *p << std::endl;
return 0;
}
sveolon@sveolon-laptop ~/build/tmp $ g++ ./main.cpp
sveolon@sveolon-laptop ~/build/tmp $ ./a.out
3
sveolon@sveolon-laptop ~/build/tmp $ g++ --version
g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright © 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

sveolon@sveolon-laptop ~/build/tmp $
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории