[c++] как правильно создавать единичные объекты?

Landstreicher

Предположим, что у меня есть некоторая сущность, которая может существовать только в одном экземпляре, причем она глобальная на всю программу (например это может быть своп, журнал сообщений). Не важно, что именно, но важно, что она одна. Еще я точно знаю когда необходимо его создать и уничтожить.
Я могу реализовать на C++ такой объект множеством способов:
1) Реализовать его классом, в котором все методы статические, есть явные функции init, done. Переменных такого класса не объявляется.
2) Реализовать его классом, в котором все методы обычные. То есть некая глобальная переменная такого типа. Поскольку инициализацию нужно проводить в четко определенный момент времени, конструктор пустой и реально инициализация делается в init.
3) Реализовать его классом, в котором все методы обычные. Но глобальная переменная является уже не просто объектом, а указателем на объект. При этом в программе происходит явная инициализация вызовом new с аргументами конструктора.
4) ...
Понятно что все эти методы работают и с практической точки зрения дают одинаковый результат. Какой из них является наилучшим с точки зрения ООП? Иными словами, при каком получается наиболее ясный, легко расширяемый, многократно используемый код?
Я сам пока вижу такие особенности:
1) -: везде надо писать static, нельзя сделать нормальный конструктор, +: быстро
2) -: нельзя сделать нормальный конструктор, +: быстро
3) -: каждый раз нужно лазить по укзателю, снижает скорость, +: нормальный конструктор
PS. Интересуют также книжки, где можно прочитать осмысленные рассуждения на подобную тематику.

maggi14

Поищи в инете на слово Singleton

Marinavo_0507

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

Dasar

лучше сделать нормальный класс + создание по первому требованию
Доступ к единичному экземпляру сделать так:

class MyClass
{
};

MyClass & MySingle
{
static MyClass * obj = new MyClass;
return *obj;
}

Landstreicher

Вопрос: как быть с инициализацией. Конструктор имеет нетривиальные аргументы. Например, для своп-файла - размер и имя файла. Как я узнаю эти параметры в функции getInstance ?
Тоже самое касается и уничтожения. Я хочу явно прибить объект в какой-то момент.

Landstreicher

Насколько я знаю (хотя я в этом плохо понимаю) в таких случаях обычно описывают шаблон, которому дают С++-класс и этот шаблон гарантирует что объект создается один раз. Сам класс при этом трогать нельзя.
У меня же немножко другая задача. Во-первых, я хочу явно создавать объект, и для этого предполагаю некоторую функцию init с некоторыми нетривиальными аргументами. При этом я беру на себя ответственность, что я вызываю init один раз. Если я вдруг вызвал второй - пусть реализация падает в segfault, разрешаю. Во-вторых, здесь в отличии от C++-шаблонов, можно менять класс. То есть я могу выделять какиее-то методы static, могу заменять конструктор на метод init итп. Здесь больше свободы.
Меня интересует, влияют ли эти 2 обстоятельства на реализацию синглетона? Может быть решение, которое оптимально в плане С++-шаблонов, будет здесь не самым оптимальным?

Dasar

пусть тогда getInstance возвращает некий object-pointer (у которого и будет Init, Done).

Landstreicher

Тогда я не понимаю, зачем создавать какие-то лишние сущности вроде getInstance. Чем это лучше чем решение из п.2 или п.3?
При таком способе сильно усложняется обращение: вместо object.doMethod(param) я все время должен писать object.getInstance.doMethod(param).

rosali


MyClass & MySingle
{
static MyClass * obj = new MyClass;
return *obj;
}
А почему бы не просто
 
MyClass & MySingle
{
static MyClass obj = MyClass;
return obj;
}
А то непонятно, кто удалять то будет...

rosali

я хочу явно создавать объект... при этом я беру на себя ответственность, что я вызываю init один раз...
Это все самообман, все эти утверждения имеют смысл пока ты программируешь _один_. Как только над проектом начнет работать несколько человек, этот подход перестанет работать...

Landstreicher

господа! все, кто приводит примеры с MySignleton, MySingle - объясните пожалуйста необходимость создания нового класса, почему нельзя эти методы включить в сам MyClass.
по поводу локального static MyClass obj - его-то как-удалять? тут по-моему вообще мрак. если сделать delete явно, то потом деструктор статического объекта сработает еще раз и получим segfault.

Landstreicher

> Это все самообман, все эти утверждения имеют смысл пока ты программируешь _один_.
> Как только над проектом начнет работать несколько человек, этот подход перестанет работать...
Не согласен. Инициализация таких глобальных объектов делается один раз в самом начале работы в функции типа main:
int main
{
SwapFile.init;
Application app = new Applicaton;
app.run;
delete app;
SwapFile.done;
}
Тут совершенно очевидно что инициализация делается один раз. Что очень важно, уничтожение делается ТУТ ЖЕ, в этом же участке кода. Нигде ни в каком другом месте программы SwapFile.init не вызывается, потому что не зачем. В такой ситуации твоя логика неприменима.

rosali

методы включить в сам MyClass
Синтаксически?
 
class MyClass
{
private:
MyClass{}
public:
static MyClass & This
{
static MyClass obj = MyClass;
return obj;
}

};
Можно, в чем проблема?
static MyClass obj - его-то как-удалять?
Семантика статических локальных переменных, то есть например

C g;
void f(void)
{
static C c = g;
}
такова.
1) Если f не разу не будет вызвана, то g не будет вызвана.
2) При первом вызове f, будет вызвана g, далее копирующий конструктор класса C. При последующих вызовах f строчка static ... будет пропускаться. Переменная c всегда будет обозначать один и тот же объект.
4) При завершении программы, если f была вызвана хоть раз, будет вызван деструктор у объекта c.
Никаких delete не нужно, потому что не было new. Память под c компилятор резервирует во время компиляции. Ну вот и все.

rosali


int main
{
SwapFile.init;
Application app = new Application;
app.run;
delete app;
SwapFile.done;
}
Ну, во-первых,
 
int main
{
SwapFile.init;
try
{
Application app = new Applicaton;
try
{
app.run;
}
catch(...)
{
delete app;
throw;
}
catch(...)
{
SwapFile.done;
throw;
}
}
Дальше обсуждать будем?

Julie16

Кстати я недавно столкнулся с тем что такой подход может некорректно работать с тредами. Так как компилятор не обязан оборачивать конструирование статического объекта в блокировки. Поэтому объект может быть проинициализирован несколько раз. Очевидным решением является в начале программы(до создания тредов) сделать A::getInstance но это не всегда работает.

rosali

Так как компилятор не обязан оборачивать конструирование статического объекта в блокировки
Ну оберни сам?..

Julie16

Легко сказать. Для этого нужно mutex статический завести. Создание которого тоже неплохо бы обернуть Можно конечно завести один глобальный mutex для этих целей, но это уже не то.

Dasar

> некорректно работать с тредами
можно добавить блокировку.

MyClass& getSafeInstance
{
AutoLock (что-то);
return getInstance;
}
MyClass & getInstance
{
MyClass obj;
return obj;
}

Julie16

Я уже указал на сложности этого подхода См. выше.

bastii

Если не влом, то можешь заглянуть в ATL, там есть СОМ объекты синглтоны, и там есть версии для различных режимов в плане многозадачности.

Landstreicher

> Семантика статических локальных переменных, то есть например
> такова.
..
> 4) При завершении программы, если f была вызвана хоть раз, будет вызван деструктор у объекта c.
> Никаких delete не нужно, потому что не было new. Память под c компилятор резервирует во время
> компиляции. Ну вот и все.
Тем самым, предложенный тобою подход оказывается не в состояниии решить предложенную задачу. Мне нужна возможность уничтожить объект в определенный момент (см. первый пост). Такой способ не позволяет это сделать.

Landstreicher

Не совсем понял о чем вы. Еще раз повторю вопрос: зачем делать какие-то специальные блокировки, если объект создается ровно один раз, известно где, известно каким тредом итп?

rosali

Ты спросил про общие принципы ООП, так вот они состоят в том, что все эти твои
объект создается ровно один раз, известно где, известно каким тредом итп
это приходящее. Через месяц ты возможно попадешь в ситуацию, когда не сможешь больше поддерживать все эти условия, которые ты навыдумывал. Вот тебе и пытаются объяснить, как заранее "подстелить соломку", чтобы потом не пришлось все переписывать. А ты только упираешься "у меня и так все работает". О чем тогда спрашивал?

Dasar

у тебя вопрос какой?
это твои слова:
Какой из них является наилучшим с точки зрения ООП? Иными словами, при каком получается наиболее ясный, легко расширяемый, многократно используемый код?
или ты про "легко расширяемы" и "многократно используемый" просто так сказал?

Landstreicher

Логично, согласен.
Но с другой стороны, мне кажется, что создавать объект при первом обращении - это неправильно. Если объект еще не создан, а программа туда лезет, то это явная логическая ошибка. Создание объекта при обращении "поощряет" такой "неконтролируемый" стиль программирования - захотели создали, захотели - не создали, и тем самым приводит к хаосу в программе, что очень плохо. IMHO у программиста должно быть четкое понимание, когда создается и уничтожается объект.
Можешь привести какой-нибудь пример, в котором это не так?
Скорее тут нужен не Singleton, а какой-нибудь CheckedPtr который при обращении по 0 не падает в segfault и не создает объект, а пишет осмысленное сообщение об ошибке или кидает exception.

Landstreicher

> или ты про "легко расширяемы" и "многократно используемый" просто так сказал?
нет, не просто так. я имел ввиду расширяемость объекта, возможность менять его внутренюю структуру. при этом то свойство, что объект существует в единственном экземпляре - одно из основных, оно не меняется. невозможно расширить false так чтобы оно стало true. поэтому мне непонятны зачем нужны всякие ухищрения, если объект, как бы он не расширялся и сколько бы он раз не использовался, все равно будет оставаться одним. чем плоха просто глобальная переменная (п.3)?

Dasar

> какой-нибудь CheckedPtr который при обращении по 0 не падает в segfault
пусть тогда getInstance возвращает некий object-pointer (у которого и будет Init, Done).
> Скорее тут нужен не Singleton
синглетон в том или ином виде тоже нужен

Dasar

1. с глобальной переменной - есть проблемы/сложности при сборке
поэтому глобальную переменную лучше оформлять, как static внутри функции
2. раз ты хочешь контролировать создание/удаление объекта - значит тебе нужен object-поинтер
3. тебе нужен один объект на программу - значит надо использовать паттерн синглетон
соответственно в результате решение будет выглядить следующим образом:
функция возвращает object-pointer, у которого есть функции Init, Done, у object-pointer-а также перегружен -> для вызова функций лога
т.е. использование выглядит как-то так:

void main
{
log.Init("out.log");
log->Write("xx");
log.Done;
}

rosali

Если объект еще не создан, а программа туда лезет, то это явная логическая ошибка
Между библиотеками бывают весьма причудливые зависимости, которые держать в голове просто немыслимо.
Для того, чтобы начать инициализацию lib1 нужна проинициализированная lib2, чтобы начать инициализацию lib2 нужны проинициализированные lib3 и lib4. Но при инициализации lib2 можно указать опцию, которая ослабляет функциональность lib2, но зато исчезает зависимость от lib4,... и короче пипец чего бывает. В Java на эту тему целая теория - в каком порядке статические конструкторы классов вызывать. Так что ты об этом подумай хотя бы день, а потом продолжим
у программиста должно быть четкое понимание, когда создается и уничтожается объект
Ну дык "создается по необходимости" -кажись предельно четко
Можешь привести какой-нибудь пример, в котором это не так?
Да, достаточно рассмотреть случай, когда ты пишешь не программу, а библиотеку, чтобы осознать многие тонкости.

Landstreicher

это уже в правильно направлении, что-то типа такого мне и надо. спасибо!
теперь думаю над таким вопросом.
если у меня есть некоторый класс SwapFile, то тут нужно создавать дополнительный класс-обертку, который содержит методы Init, Done, operator -> итп. Очень хочется создать эту обертку как шаблон. Но у разных классов конструкторы содержат разное число элементов. Мне же нужно объявить функцию Init с числом (и типом) аргументов соответствующим конструктору. Получается, нужно какой-то шаблон с переменным числом параметров-типов. Как такое сделать?

Landstreicher

Между библиотеками бывают весьма причудливые зависимости, которые держать в голове просто немыслимо.
Для того, чтобы начать инициализацию lib1 нужна проинициализированная lib2, чтобы начать инициализацию lib2 нужны проинициализированные lib3 и lib4. Но при инициализации lib2 можно указать опцию, которая ослабляет функциональность lib2, но зато исчезает зависимость от lib4,... и короче пипец чего бывает. В Java на эту тему целая теория - в каком порядке статические конструкторы классов вызывать. Так что ты об этом подумай хотя бы день, а потом продолжим
Мрак...
Мне всегда казалось что подобные вещи - следствие плохого дизайна, кривости, итп.
Или ты можешь привести какие-то ситуации, в котором даже при правильном дизайне подобные глюки все равно неизбежно возникают?

Dasar

> следствие плохого дизайна, кривости, итп.
Файловой библиотеке нужен менеджер памяти и библиотека синхронизации.
Библиотеке синхронизации нужен менеджер памяти и логгер.
Менеджеру памяти нужна библиотека синхронизации.
Логгеру нужна файловая библиотека, библиотека синхронизации и менеджер памяти.
Каким здесь будет хороший дизайн?
Оставить комментарий
Имя или ник:
Комментарий: