Здравствуйте, MescalitoPeyot, Вы писали:
MP>Здравствуйте, pegarus, Вы писали:
P>>Подскажите, как называется приведённая ниже реализация синглтона? Какие есть недостатки в отличие от синглтона Маерса?
MP>Она называется велосипед с проблемами. Например, не потокобезопасный счетчик первое что бросается в глаза
+1
Однако, преимущества этого подхода перед СМ есть.
operator-> делает более сложным сохранение ссылки/указателя на синглтон.
Т.е. в случае X::Instance().Method() можно написать X& x = X::Instance(); в случае X::instance->Method() это уже сложнее.
Ссылки вида CSingle считаются, так, в отличии от СМ, ситнглтон не будет использован после удаления, а после удеаления последней ссылки если он снова нужен он снова создаётся (разумеется, не-потокобезоасность всё ломает).
Далее, в отличии от СМ этот, похоже, готов к бросающему конструктору.
---
Я пришел к выводу, что лучше синглтон с таким интерфейсом в хедере:
Т.е. никаких приватных методов и дата мемберов (как преимущества pImpl), плюс нельзя вообще никак взять его адрес или попытаться скопировать.
И меньше возможностей нарушить ABI незаметно для компоновщика в случае, например, TCHAR дата memberов:
class X : public Singleton<X>
{
...
TCHAR path[MAX_PATH];
} // имплементация c _UNICODE, инстанциируем без _UNICODE - happy debugging :)
Имплементация в .cpp в зависимости от потребностей. Неплохо её запихнуть в глобальную переменную в неймспейс детейл:
(в том числе и в минидампе с MiniDumpWithDataSegs)
Т.к. статической инициализации редко достаточно, нужно как-то обеспечить динамическую инициализацию/финализацию. Тут в зависимости от потребностей, разное можно сделать: boost::call_once, что-то вроде Синглтона Мейерса, но форсированое конструктором глобального объекта, более интересные трюки. Для финализации может быть полезен прямой вызов atexit.
Т.е. имплементация каждый раз по месту, зависит от конкретного случая, и может быть изменена действительно незаметно для клиента — без перекомпиляции его кода. И никакой шаблонной магии.
Где я бы такое применял. Можно условно разделить синглтоны на:
1. "Глобальные переменные" — менеджеры памяти, врапперы к системным сервисам, жучки для отладки — то что действительно по-любому синглтон. Тут вышеописанный интерфейс синглтона рулит.
2. "Ошибки проектирования" — Классы, содержащие логику приложения, которые всвязи с текущим дизайном сделаны синглтонами, но дизайн может поменятся. Тут лучше сделать синглтонность CRTP-базой.
Здравствуйте, pegarus, Вы писали:
P>Подскажите, как называется приведённая ниже реализация синглтона? Какие есть недостатки в отличие от синглтона Маерса?
Она называется велосипед с проблемами. Например, не потокобезопасный счетчик первое что бросается в глаза
Здравствуйте, MescalitoPeyot, Вы писали:
MP>Здравствуйте, pegarus, Вы писали:
P>>Подскажите, как называется приведённая ниже реализация синглтона? Какие есть недостатки в отличие от синглтона Маерса?
MP>Она называется велосипед с проблемами. Например, не потокобезопасный счетчик первое что бросается в глаза
Да, об этом там прямо и сказано... Какие ещё проблемы усматриваются?
Здравствуйте, pegarus, Вы писали:
P>Да, об этом там прямо и сказано... Какие ещё проблемы усматриваются?
CSignle копируются через сгенерированный конструктор копий, при этом число ссылок не увеличивается, но при уничтожении число ссылок уменьшается.
CSingle<X> a;
CSingle<X> b(a);
Ещё, желательно делать CRTP-базой, а не просто шаблоном, чтобы запретить создание экземпляра другим способом. Типа, сейчас можно так:
CSingle<X> a;
X b; // создали не через синглтон.
X c(*(a.operator->())); // скопировали синглтон. разумеется, от такого можно не защищаться, но в зависимости от реализации X, могут быть другие способы копирования, так что копирование может быть непреднамеренным.
Здравствуйте, pegarus, Вы писали:
P>Да, об этом там прямо и сказано... Какие ещё проблемы усматриваются?
Ещё, усматривается проблема, которой нет даже в Синглтоне Мейерса
template CSingle<CBase>::tSingle single;
Это объект со статическим временем жизни с конструктором.
Обращения к синглтону могут быть до вызова конструктора.
Тогда они будут вполне успешны, но вызов
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, pegarus, Вы писали:
P>>Да, об этом там прямо и сказано... Какие ещё проблемы усматриваются?
AG>Ещё, усматривается проблема, которой нет даже в Синглтоне Мейерса
AG>
template CSingle<CBase>::tSingle single;
AG>Это объект со статическим временем жизни с конструктором. AG>Обращения к синглтону могут быть до вызова конструктора. AG>Тогда они будут вполне успешны, но вызов AG>
CSingleImpl() : pn(0),px(0) {}
AG>удалит существующий объект.
AG>лучше просто выкинуть эти строки: AG>
Спасибо — согласен, обращения могут быть. Однако, это признак неверной архитектуры — фактически, наличия "сырых" синглтонов через static. То есть лучше добавить в конструктор инициализацию некоей a=0xABCD, и в обращениях проверять assert(a==0xABCD)
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, pegarus, Вы писали:
P>>Да, об этом там прямо и сказано... Какие ещё проблемы усматриваются?
AG>CSignle копируются через сгенерированный конструктор копий, при этом число ссылок не увеличивается, но при уничтожении число ссылок уменьшается. AG>
AG>CSingle<X> a;
AG>CSingle<X> b(a);
AG>
Про копирование это очевидно (просто не стал загромождать код — также, как и поддержкой многопоточности =), но всё равно спасибо!
AG>Ещё, желательно делать CRTP-базой, а не просто шаблоном, чтобы запретить создание экземпляра другим способом. Типа, сейчас можно так: AG>
AG>CSingle<X> a;
AG>X b; // создали не через синглтон.
AG>X c(*(a.operator->())); // скопировали синглтон. разумеется, от такого можно не защищаться, но в зависимости от реализации X, могут быть другие способы копирования, так что копирование может быть непреднамеренным.
AG>
За подсказку с CRTP спасибо — однако, такой задачи не стояло; на практике я обхожу этот момент без CRTP, "затачиванием" требуемого класса под CSingle изначально:
Здравствуйте, pegarus, Вы писали:
P>Признаю, CRTP здеь вполне уместен =)
В Boost Signleton ((пока что) не принятый в boost) сделано так, что у CBase предуется не дефолтный конструктор, а конструктор с параметром restriced, и синглтон — CRPT база, это позволяет не загромождать класс вот этим:
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, pegarus, Вы писали:
P>>Здравствуйте, Alexander G, Вы писали:
AG>>>Здравствуйте, pegarus, Вы писали:
P>>>>Да, об этом там прямо и сказано... Какие ещё проблемы усматриваются?
AG>>>Ещё, усматривается проблема, которой нет даже в Синглтоне Мейерса
AG>>>
template CSingle<CBase>::tSingle single;
AG>>>Это объект со статическим временем жизни с конструктором. AG>>>Обращения к синглтону могут быть до вызова конструктора. AG>>>Тогда они будут вполне успешны, но вызов AG>>>
CSingleImpl() : pn(0),px(0) {}
AG>>>удалит существующий объект.
AG>>>лучше просто выкинуть эти строки: AG>>>
P>>Спасибо — согласен, обращения могут быть. Однако, это признак неверной архитектуры — фактически, наличия "сырых" синглтонов через static. То есть лучше добавить в конструктор инициализацию некоей a=0xABCD, и в обращениях проверять assert(a==0xABCD)
AG>можно даже ничего не добавлять AG>template CSingle<CBase>::tSingle single = {-1, 0}; AG>и проверять на pn == -1.
AG>я бы таки разрешил использование до main, тогда вместо конструктора статический инициализатор: AG>template CSingle<CBase>::tSingle single = {0, 0}; AG>, при этом нули неявно подразумеваются AG>template CSingle<CBase>::tSingle single = {}; AG>и сам инициализатор тоже AG>template CSingle<CBase>::tSingle single;
P>>Ещё замечания?
AG>Мне не нравится название шаблонного параметра CBase. AG>Как планируется применять этот синглтон ?
На самом деле я его давно уже применяю (только добавлен ещё приватный конструктор копирования и мьютекс для многопоточности).
Применение всегда такое:
typedef CSingle<CObj> CSingleObj;
class CKlass1
{
...
CSingleObj obj;
...
};
...
class CKlass2
{
...
CSingleObj obj;
...
};
Вообще, основная идея кода состоит в отказе от "ручного" управления временем жизни, поэтому все указатели и синглтоны связаны с "хозяином" — то есть все указатели умные (жаль, пока нельзя перегрузить оператор точки). Весь код, в итоге, выглядит как объявление статических (не путать со static) объектов, без явного использования new/delete/Instance/AddRef/Release. В отличие от Маерса, нет надобности следить за моментом Release/Instance
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, pegarus, Вы писали:
P>>просто не стал загромождать код — также, как и поддержкой многопоточности
AG>поддержка многопоточности это не мелочь вообще-то. можно ошибиться именно в ней.
Да, если наше приложение многопоточное... Защита сводится к добалению критических секций (паттерн Guard) в вызовы:
(также добавил закрытый конструктор копирования)
/* Файл Single.h
Класс не допускает наследование.
Нежелательно динамическое размещение (assert'ы как раз из-за этого =).
Создаётся только один экземпляр CBase.
Не требует инстанциирования шаблона.
Поддерживает многопточность.
CBase должен иметь конструктор без параметров.
*/#include <Guard.h> // реализует паттерн Guard, включает <Mutex.h> - реализовано через ACEtemplate <class CBase>
class CSingle
{
public:
CSingle() : pcache(single.instance()) {}
CBase* operator ->()
{
return pcache;
}
~CSingle() { single.release(); }
private:
CSingle(const CSingle<CBase>&) {assert(0);}
CBase* pcache;
template<CBase>
class CSingleImpl // Введён для упрощения инстанциирования
{
public:
CSingleImpl() : pn(0),px(0) {}
CBase* instance()
{
Auxiliary::CGuard locker(cs);
if(!pn) px=new CBase();
pn++;
}
void release()
{
Auxiliary::CGuard locker(cs);
assert(pn>=0); // Поможет отловить утечки
pn--;
if(!pn) delete px;
}
~CSingleImpl() {assert(!pn);} // Поможет отловить утечкиprivate:
Auxiliary::CMutex cs;
int pn;
CBase* px;
};
typedef CSingleImpl<CBase> tSingle;
static tSingle single;
};
template CSingle<CBase>::tSingle single;
Замечу, что использование CMutex затрудняет идею вызова "до main" со статической инициализацией pn/px (а не в конструкторе), т.к. мьютекс будет неинициализирован (плюс там ещё необходимо выполнить инициализацию ACE).
Предложите своё название для CBase? =) Здесь — "база синглтона"
Re[2]: Синглтон через умный указатель
От:
Аноним
Дата:
09.03.09 10:08
Оценка:
Здравствуйте, MescalitoPeyot, Вы писали:
MP>Она называется велосипед с проблемами. Например, не потокобезопасный счетчик первое что бросается в глаза
A кто сказал, что синглтона Маерса потокобезопасен?
Здравствуйте, <Аноним>, Вы писали:
А>A кто сказал, что синглтона Маерса потокобезопасен?
СМ в простейшей реализации настолько прост, что его сложно заподозрить в поддержке многопоточности. А вот в варианте топик-стартера имеем непотокобезопасный счетчик ссылок в умном указателе, что естественно вызывает вопросы (если уж указатель считает ссылки, почему бы это ему не делать правильно, тем более что это не так уж и сложно).
Впрочем, если б я заметил
Здравствуйте, Alexander G, Вы писали:
AG>Имплементация в .cpp в зависимости от потребностей. Неплохо её запихнуть в глобальную переменную в неймспейс детейл:
Согласен. Лучше даже в unnamed, только тогда с отладкой туго...
AG>Т.к. статической инициализации редко достаточно, нужно как-то обеспечить динамическую инициализацию/финализацию.
Как показывает опыт, бывает несложно определить "время потребности" в таком singleton. Тогда достаточно пары простых методов — initialize/cleanup.
Но иногда всё таки требуется отложенная инициализация с многопоточностью и произвольным временем доступа (например, аллокаторы — никто не запрещает заполнять контейнер при инициализации статики, те же глобальные const std::string x = "xxx" и т.п.). Как я понял, универсального метода создания такого singleton нет, всё упирается в неатомарную инициализацию объектов синхронизации. Пока обхожусь такой вот конструкцией:
S>Но иногда всё таки требуется отложенная инициализация с многопоточностью и произвольным временем доступа (например, аллокаторы — никто не запрещает заполнять контейнер при инициализации статики, те же глобальные const std::string x = "xxx" и т.п.). Как я понял, универсального метода создания такого singleton нет, всё упирается в неатомарную инициализацию объектов синхронизации. Пока обхожусь такой вот конструкцией:
Точнее, здесь получается не отложенная инициализация, а специфическое время жизни объекта — от первого обращения или инициализации статики до закрытия приложения. Кстати, может следует таким образом инстанцировать общий lock, а затем честно пользовать отложенную инициализацию через double check locking pattern? И всё равно непонятно, как быть с удалением...
Здравствуйте, sokel, Вы писали:
S>Здравствуйте, sokel, Вы писали:
S>>Но иногда всё таки требуется отложенная инициализация с многопоточностью и произвольным временем доступа (например, аллокаторы — никто не запрещает заполнять контейнер при инициализации статики, те же глобальные const std::string x = "xxx" и т.п.). Как я понял, универсального метода создания такого singleton нет, всё упирается в неатомарную инициализацию объектов синхронизации. Пока обхожусь такой вот конструкцией:
S>Точнее, здесь получается не отложенная инициализация, а специфическое время жизни объекта — от первого обращения или инициализации статики до закрытия приложения. Кстати, может следует таким образом инстанцировать общий lock, а затем честно пользовать отложенную инициализацию через double check locking pattern? И всё равно непонятно, как быть с удалением...
Упс, про double check наврал, она в любом случае небезопасна. В общем, нужен быстрый метод получения объекта и в этом плане меня моя поделка устраивает. Минусы — отсутствие возможности контроля удяления. Но этим иногда можно пожертвовать.
Здравствуйте, sokel, Вы писали:
S>Здравствуйте, sokel, Вы писали:
S>>Здравствуйте, sokel, Вы писали:
S>>>Но иногда всё таки требуется отложенная инициализация с многопоточностью и произвольным временем доступа (например, аллокаторы — никто не запрещает заполнять контейнер при инициализации статики, те же глобальные const std::string x = "xxx" и т.п.). Как я понял, универсального метода создания такого singleton нет, всё упирается в неатомарную инициализацию объектов синхронизации. Пока обхожусь такой вот конструкцией:
S>>Точнее, здесь получается не отложенная инициализация, а специфическое время жизни объекта — от первого обращения или инициализации статики до закрытия приложения. Кстати, может следует таким образом инстанцировать общий lock, а затем честно пользовать отложенную инициализацию через double check locking pattern? И всё равно непонятно, как быть с удалением...
S>Упс, про double check наврал, она в любом случае небезопасна. В общем, нужен быстрый метод получения объекта и в этом плане меня моя поделка устраивает. Минусы — отсутствие возможности контроля удяления. Но этим иногда можно пожертвовать.
А всё-таки, что мешает использовать обычный мьютекс? Для защиты указателя и счётчика ссылок (ниже есть код). Никак не пойму, вижу только намёки... ???
Здравствуйте, pegarus, Вы писали:
P>А всё-таки, что мешает использовать обычный мьютекс? Для защиты указателя и счётчика ссылок (ниже есть код). Никак не пойму, вижу только намёки... ???
Во первых, хотелось бы избежать использования мьютекса при каждом получении instance. Во вторых, не всегда можно статически инициализировать этот самый мьютекс, что опять таки возвращает нас к проблеме singleton-мьютекса.
Допустим, мы подобным образом, создали общий мьютекс для создания всех singleton объектов:
Несостоятельность double check locking pattern обычно объясняют тем, что инструкции могут быть переупорядочены, то есть подобный код в общем случае не является потокобезопасным:
Здесь вызов конструктора и присваивание указателя могут поменяться местами.
Интересно, а если создание объекта вынести во внешнюю функцию, запретив тем самым inlining конструктора объекта, не будет ли это являться универсальным memory barrier (если считать присваивание указателя атомарной операцией), например: