[c++] порядок создания классов-синглтонов

elenangel

возникла такая задачка. есть класс A и шаблон B, объявленный как потомок AbstractB. оба синглтоны. при инстанциировании шаблона в конструкторе он добавляет свой this в список класса A. объявлено это дело таким образом:

//test.h
#ifndef TEST_H
#define TEST_H

#include <list>

using namespace std;

class AbstractB;

class A
{
    friend class AbstractB;
protected:
    void addB(AbstractB* p);
public:
    static A *getInstance;
    //...

private:
    A;
    A(A&);
    ~A;
    A& operator=(A&);
    static A *manager;
    list<AbstractB*> pList;
};

class AbstractB
{
    friend class A;
public:
    AbstractB;
//...
};

template<typename T>
class B:public AbstractB
{
public:
    static B<T>* getInstance;
protected:
    static B<T> g_Instance;
private:
    B:AbstractB{}
    B& operator=(B&) {}
    B(const B&) {}
};

template<typename T> B<T> B<T>::g_Instance;

template<typename T>
B<T>* B<T>::getInstance
{
    return &g_Instance;
}

#endif // TEST_H


//test.cpp

#include "test.h"

A *A::manager = 0;

A::A
{
}

A::A(A&)
{
}

A::~A
{
}

A& A::operator=(A&)
{
    return *this;
}

A *A::getInstance
{
    if(manager)
    {
     return manager;
    }
    else
    {
     manager = new (nothrow) A;
     if(!manager)
     {
     //LOG_BEGIN(ERROR_LOG|1);
     //LOG("Can't create an instance of A");
     //LOG_END;
     }
     return manager;
    }
}

void A::addB(AbstractB* p)
{
    pList.push_back(p);
}

AbstractB::AbstractB
{
    A *instance = A::getInstance;
    if(instance)
    {
     instance->addB(this);
    }
    else
    {
     //LOG_BEGIN(ERROR_LOG|1);
     //LOG("Can't get an instance of A");
     //LOG_END;
    }
}


так вот, изначально в классе A тоже его единственный экземпляр был объявлен не как указатель, а как сам класс, но получалось что к моменту инстанциирования шаблона он еще не создан, а конструктор B уже пытается в его список указатель на себя добавить.
можно ли как-то заставить с++ вначале создавать синглтон A, и только после него создавать инстансы B<>?

okunek

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

elenangel

а можно более развернуто - в чем именно пиздец? в идее так делать синглтоны? регать их из одного класса в другом в конструкторе?

okunek

A и B у тебя связаны сильно, да еще и друг у друга френды. Синглтон не безпроблемный объект. А когда ты их два сделал, да еще и связал вместе, то это бомба замедленного действия: ты сейчас свои проблемы решишь как-нибудь, а потом надо будет что-нибудь добавить и единственное полезное, что можно будет сделать с кодом - выкинуть его.
Сходу могу предложить сделать фабрику классов B, которая заодно и регать будет их у A. Экземпляр A (указатель или ссылку будешь подавать в конструкторе фабрики, поэтому гарантированно A будет создаваться раньше B.
Хотя встанет вопрос, где будет жить фабрика... ну в таком случае и A туда запихнуть и пусть фабрика живет синглтоном.

elenangel

B - это не класс, а шаблон, когда нужен экземпляр B, его нужно от шаблона получить, и он должен быть синглтоном, потому что в нем идеологически необходимы 2 статические функции, каждая из которых должна иметь доступ к экземпляру своего класса. как я сделаю фабрику шаблонов?
вообще это задача такая. я ничего более умного не придумал. A - это объект который регистрирует службы Windows, а B - это собственно сама служба. Сделать ее простым наследованием без шаблонов можно, но придется сделать грязый хак - передачу ссылки на экземпляр в потоке кода с залезанием на низкий уровень и вставкой кода в виде шестнадцатиричных байтов в скомпилированном виде с подстановкой куда-то указателя.

okunek

Шаблонный класс - это тоже класс.
В фабрике сделай шаблонный метод, содержащий статическую переменную типа B<T> и возвращай ссылку или указатель на него, перед этим регистрируя в экземпляре A. Если экземпляр A у тебя будет объявлен статическим мембером, то он гарантированно будет инстанцироваться раньше всех B<T>. Btw: в стандарте есть какие-то фишки на тему отложенной инициализациии статиков, но я их уже не помню и по-идее, они не должны тут проявиться.
Хотя если фабрика будет синглтоном, то A делать статиком не обязательно, просто создать в конструкторе.

elenangel

спасибо за подсказку, вроде бы понял твою мысль, попробую реализовать.
еще пришло в голову что можно список объектов перенести статиком в AbstractB и в нем регистрироваться, а A останется френдом и будет оттуда список забирать когда ему надо, вроде бы это тоже должно решить проблему с порядком создания.

okunek

Нет, не поможет. Ты фактически возвращаешься к своему первому посту, только в другом виде. Если ты определяешь статические мемберы классов или глобальные переменные, то тебе нельзя закладываться на порядок их инициализации. Единственное, на что можно рассчитывать, так это если ты одну переменную инициализируешь компайл тайм константой, а вторую ты вычисляешь вызовом функции. Тогда первая будет проинициализирована сначала. Опять же, за подробностями к стандарту.
И насчет френдов, найди все причины, по которым ты решил добавить френдов и избавься от них(причин и френдов). Я думаю, должно получиться. При хорошем проектировании классов, в подавляющем большинстве случаев френды не нужны.

elenangel

а разве статик мембер предка не проинициализируется гарантированно раньше мемберов потомка, в частности к моменту вызова конструктора потомка?
насчет френдов - без них можно обойтись, если добавить в явном виде функцию регистрации класса B в A в паблик, но она во внешнем интерфейсе строго говоря не нужна и должна быть спрятана. Тут вопрос что хуже - выставлять явно внутренние методы в паблик или френдить 2 класса. Либо еще наверно можно пронаследоваться от общего предка с защищенной виртуальной функцией, но в одном из классов эта функция окажется лишней, унаследуется, так сказать в нагрузку.

okunek

Нет, наследование здесь ни при чем. Запись static blabla; в классе - это декларейшн. Дефинишн будет за пределами класса. И там уже предок, потомок, не имеет значения. Порядок для предков/потомков имеет значение только для нестатических мемберов во время инстанцирования самого класса, а не создания каких-то там статических мемберов.
> A - это объект который регистрирует службы Windows
Назначение класса как раз говорит о том, что функция регистрация должна быть публичной.
Зачем, кстати, класс AbstactB объявлен френдом в A?

elenangel

у AbstractB есть защищенные виртуальные функции
typedef void (__stdcall *winServiceProcTypeDWORD, char**); // указатель на функцию службы Windows
virtual winServiceProcType getServiceProc = 0; // возвращает указатель на статик функцию данной инстанции шаблона
virtual char* getCharServiceName = 0;
которые определяются в шаблоне-потомке.
class A вызывает их в своем публичном методе RegisterServices в рантайме когда все шаблоны проинстанциированы и добавлены в его список.
а вот метод B<T>.RegisterService (это который сейчас описан как AddB в предке) вызывать руками из рантайма не хочется, хочется чтоб он был спрятан и вызывался автоматически. потому они друг другу и френды.

okunek

Я щас мало что смогу придумать, надо порисовать квадратики, но могу предложить тебе вот что. Отнаследуйся от B<T>, пусть это будет, например RegB<T>. И в этом классе сделай публичный метод регистрации. В фабрике создавай именно его, RegB<T>, а наружу возвращай только B<T>. И RegB<T> передавай только в A, чтобы тот смог вызывать RegisterService.
Ну и в качестве хинта, создавай не сами объекты, а шаред птр на объект. Указатель можно удалить, а ссылку можно забыть поставить, что приведет к двум удалениям объекта. А шаред птр втоде как понадежнее.

Serab

Дефинишн будет за пределами класса
и если в одной единице трансляции, то инициализация будет в гарантированном порядке происходить.

Serab

хотя это опасное знание, лучше его не распространять :)

okunek

гарантированном порядке происходить
Ага. Только порядок будет известен после компиляции.

Maurog

Ага. Только порядок будет известен после компиляции.
не мути воду, порядок инициализации в одной единице трансляции строго определен стандартом
3.6.2 Initialization of non-local objects [basic.start.init]
1 Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization
takes place. Zero-initialization and initialization with a constant expression are collectively called static
initialization; all other initialization is dynamic initialization. Objects of POD types (3.9) with static storage
duration initialized with constant expressions (5.19) shall be initialized before any dynamic initialization
takes place. Objects with static storage duration defined in namespace scope in the same translation
unit and dynamically initialized shall be initialized in the order in which their definition appears in the
translation unit.
[Note: 8.5.1 describes the order in which aggregate members are initialized. The initialization
of local static objects is described in 6.7. ]
3.7.1 Static storage duration [basic.stc.static]
1 All objects which neither have dynamic storage duration nor are local have static storage duration. The
storage for these objects shall last for the duration of the program (3.6.2, 3.6.3).

okunek

Извините, никогда не закладывался на порядок инициализации нелокальных переменных :)

state7401281

люблю я такие треды, где про c++, синглтоны, шаблоны и еще что-то. я совершенно не понимаю о чем здесь написано, и даже в том коде, который тут приведён я не смог разобраться. от этого обсуждаемая тема выглядет более значимой и даже где-то местами поддувает легкий сквознячок элитности. до самого последнего поста не покидает ощущение, что предмет обсуждения как-то связан ни то с hi-tech'ом послезавтрашнего дня ни то с филосовскими знаниями атлантов. но ради всего сущего, объясните мне уже кто-нибудь:
О ЧЕМ ЭТОТ ТРЕД И ГДЕ ЭТО ПРИМЕНЯЕТСЯ?

elenangel

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

slonishka

BULLSHIT!

Werdna

using namespace std;
Так пишут только пидорасы.
Если тебя задалбывает писать в определённом cpp'нике std::string, то можно написать using std::string. Если уж совсем-совсем, хотя я не знаю такой ситуации, можно написать using namespace, но не в h-файле же? Ты же тем самым уничтожаешь всё то, для чего был создан неймспес!
По теме: синглтоны пишут только пидорасы. Откуда эта тяга за меня решать сколько экземпляров класса создавать?

Serab

По теме: private пишут только пидорасы. Откуда эта тяга за меня решать, какие методы мне можно вызывать, а какие нет?

Werdna

Есть и такая точка зрения, уверяю. Бачан, например, считает что писать прайветы — не круто.
Тем не менее разница просто принципиальная. Приматное поле или приватный метод — это то, что ты хочешь спрятать, чаще всего это РЕАЛИЗАЦИЯ. Конечно же, глупо делать поле приватным и потом давать два метода set и get, так делают пидорасы. Но если там что-то происходит ещё, то это нормально.
А вот синглтоны — пидерастия в чистом виде. Почему я не могу создать ДВА экземпляра класса если я это хочу? Это может быть с чем угодно связано.

Werdna

BULLSHIT!
Опередил, мерзавец! :)
Смотри, кстати, хомячок люто и бешенно минусуют. Вирус острого синдрома ООП прямо...

elenangel

я предполагаю, что у тебя больше опыта, чем у меня. предложи решение обозначенной проблемы без синглтонов и прочего.
в идеале хочется примерно следующего:

class MyService:public WinService
{
void OnStart; // перекрытая виртуальная
void OnStop; // перекрытая виртуальная
}

void OnStart
{
// Здесь десериализуем какие-то данные, начинаем слушать TCP порт, принимаем подключения, запускаем рабочий цикл службы в котором делаем что-то полезное.
LOG_BEGIN(EVENT_LOG|1);
LOG("Service started (name)");
LOG(this->serviceName);
LOG_END;
}

void OnStop
{
//Здесь завершаем потоки службы, останавливаем TCP сервер, сериализуем данные.
LOG_BEGIN(EVENT_LOG|1);
LOG("Service stopped (name)");
LOG(this->serviceName);
LOG_END;
}

int main(int ac, char ** av)
{
MyService svc1,svc2;
svc1.setName("name1");
svc2.setName("name2");
SCM scm; // service control manager
scm.add(svc1);
scm.add(svc2);
scm.registerServices;
return 0;
}

надеюсь, примеры того какой API нужен для регистрации сервиса и какие функции нужно им передать писать не нужно.

Serab

void OnStart; // перекрытая виртуальная
void OnStop; // перекрытая виртуальная
нахуа писать эти комментарии? потому что лень написать virtual, ведь это не обязательно, а без него — непонятно, поэтому комментарии?

elenangel

потому что при перекрытии не нужно писать virtual

Serab

это кто сказал?

Serab

Есть и такая точка зрения, уверяю. Бачан, например, считает что писать прайветы — не круто.
Бачан считает, что писать class не круто, мы не об этом сейчас.
Тем не менее разница просто принципиальная. Приматное поле или приватный метод — это то, что ты хочешь спрятать, чаще всего это РЕАЛИЗАЦИЯ.
Конечно же, глупо делать поле приватным и потом давать два метода set и get, так делают пидорасы. Но если там что-то происходит ещё, то это нормально.
другого выхода нет, иначе получается неподдерживаемая шняга, в том-то и дело, что поле — это РЕАЛИЗАЦИЯ set/get, т.е. свойства. Она-то и может измениться. А если все настолько ясно, что меняться не будет, хуячь структуру.
А вот синглтоны — пидерастия в чистом виде. Почему я не могу создать ДВА экземпляра класса если я это хочу? Это может быть с чем угодно связано.
Почему я не могу заиметь доступ (кстати, я — могу :grin:) к приватным данным, это может быть с чем угодно связано.
Не, синглтоны — уебищно, но ты причин этому не привел. Хотелки — это не аргумент.

bleyman

предложи решение обозначенной проблемы без синглтонов и прочего.
в идеале хочется примерно следующего:
Ну так и напиши именно это... Что ты там хочешь делать синглтоном?

elenangel

OnStart должна вызываться из функции, условно назовем ее serviceProc, которая регистрируется в винде, вызывается виндой и содержит параметры типа int и char**, уговорить винду передать мне в этих параметрах this чтобы вызвать OnStart я не могу, serviceProc - статическая, ей this конечно же не передается. Передавать this в глобальной переменной - а как тогда создавать больше 1 экземпляра класса? отсюда синглтон, он лучше чем глобальная переменная имхо. а чтобы можно было более 1 службы делать - делаем шаблон этого синглтона и инстанцируем от какого-нибудь класса - не принципиально какого, но можно наверно даже на него что-то полезное навесить.

Serab

блиа, да тут повторяется ситуация с тем комментом, зачем-то впечатался синглтон (не пишем virtual а чтобы из этого выпутаться, создается шаблон (коммент) :ooo:
Кстате, если уж на то пошло, то синглтон включает две составляющих: глобальный доступ и ограничение инстанцирования. Не обязательно для получения глобального доступа ограничивать инстанцирование, либо наоборот.

Werdna

я предполагаю, что у тебя больше опыта, чем у меня. предложи решение обозначенной проблемы без синглтонов и прочего.
А нужно ли там вообще делать класс?
Ты tcp-сервер пишешь? Я вообще не понимаю зачем заворачивать в класс то, что создаётся в единственном экземпляре. Хотя сам иногда заворачиваю, но делаю просто методы start/stop и прочие.
Ну т. е. ты опиши задачу, и попытайся сделать максимально просто. Не пытайся применить весь комплекс знаний которые у тебя есть, простое решение найди, оно же будет работать. А что тебе ещё надо от программы?

elenangel

Ну во-первых мне нужно все-таки 2 службы для двух таргетов одного проекта - клиент и сервер, хочется использовать общий код. Класс я начал делать чтобы не писать один код 2 раза и потому, что не думал, что это окажется таким адским геморроем, думал там не сложнее чем, к примеру тред в класс завернуть. Ну и задача сама по себе довольно интересная в том плане, чтобы найти красивое решение. В конечном итоге я нашел варианты решения - 1) поменять порядок объектных файлов в мейкфайле, это поменяет порядок инициализации статик членов на нужный (фиговый компайлеро-зависимый вариант); 2) отказаться от регистрации класса в конструкторе, перенеся это в getInstance который проверяет флаг зарегистрированности экземпляра, установленный в false в конструкторе - кривовато, но не зависимо от компайлера. 3) не делать статик экземпляры, а делать статик указатели на них и создавать на куче - не нравится, потому что не понятно что делать в конструкторе, если память не выделилась - код возвращать некуда, а кидание эксепшенов в этом проекте не одобряется. С фабриками делать не стал, мне кажется это еще сильнее усложнит эту часть программы, которую изначально надеялся решить вообще одним классом.

Werdna

мне нужно все-таки 2 службы для двух таргетов одного проекта
Мне кажется, что ты с терминологией перегнул. Я вот не понимаю что такое "два таргета одного проекта". И что такое "служба"?
Класс я начал делать чтобы не писать один код 2 раза

Не бойся, пиши 10 раз, это не страшно. На одиннадцатый придёт какое-то решение, которое будет на голову выше остальных. Красивый код рождается не в муках поиска, а сам собой во время практики.
Ну и вообще, ты так и не описал задачу. У тебя классы, фабрики, службы... что хоть делаешь? Подыми голову, посмотри вокруг.

Werdna

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

elenangel

2 таргета - это две цели сборки, два результирующих экзешника. один из экзешников это сервер, другой - клиент, оба должны запускаться в фоновом режиме не зависимо от того сделал ли пользователь windows logon. У них большАя часть кода общая, но не вся, соответственно 2 ф-и main в проекте, target - это как оно называется в kdevelop.
Служба - это service win32, та для которой можно делать net start и net stop.

elenangel

госпадикакойбред
контрнебред в студию. а то теоретически критиковать каждый горазд.

Serab

не понятно что делать в конструкторе, если память не выделилась - код возвращать некуда, а кидание эксепшенов в этом проекте не одобряется
охеть! Действительно непонятно! А знаете, почему? Потому что исключения как раз и созданы, чтобы их кидать из конструктора! Не хотите исключений — не используйте конструкторы C++ :)

Serab

ладно, тут надо помочь с проблемой, а все начали обсирать :( Простите :(

elenangel

не кидать исключения - не мое решение.

elenangel

с проблемой можно не помогать, работающее решение таки есть.
теперь хочется красивое решение, но это уже опционально и не к спеху.

Serab

хотя нет, я уже предложил подход: надо решить, зачем синглтоны тут нужны? Для глобальной точки доступа или для ограничения количества? Теоретически вот нафига ограничивать количество? Так ли это обязательно? Попробуй от этого отказаться (в надежде, что никто и не будет пытаться создавать второй экземпляр а потом и от глобальности. И нормально В main сконфигурируй эти свои классы, а дальше уж как-нибудь их пропихни куда надо... Не все ж синглтонами писать...

Serab

а, т.е. уже можно обсирать? :bat:

elenangel

ага, только конструктивно, а не в стиле "ООП используют одни пидарасы"

okis

не кидать исключения - не мое решение.
т.е. если в конструкторе проблема — флаг какой-то выставляется, или что?

elenangel

нет, по возможности избегается код в конструкторе, который может вызвать проблемы.

Serab

Ну да, делайте тогда методы Init...

okis

Давай разбираться. Это означает, что память в конструкторе на стеке выделяется?

elenangel

примерно так и делается. но имхо это не красиво.

elenangel

да, на стеке. или в сегменте данных.
Оставить комментарий
Имя или ник:
Комментарий: