Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Dependency Injection


Автор: SABROG 12.8.2010, 21:44
Пытаюсь сделать "правильный" дизайн приложения. Есть классы типа Settings, DownloadManager, ScriptManager, JobManager.
И 3 класса должны иметь доступ к Settings, чтобы читать и писать свои данные. Напрашивается синглтон, но мы знаем, что это антипаттерн и поэтому он быстро "распрашивается" обратно. Альтернатива - передача указателя на Settings в конструктор каждого объекта (или через метод setSettings, что менее рекомендуемо). По сути советуют использовать Dependency Injection / Inversion Of Control (IoC) (пока разницы не понял, говорят, что одно частный случай другого). Сам паттерн не ограничивается передачей указателя на экземпляр класса, это должен быть указатель на интерфейс для конкретного класса типа ISettings, чтобы жестко не привязывать объекты друг к другу (забыл как называется это принцип ООП). Собственно на этой стадии реализация паттерна не идеальна (уже не помню почему), поэтому предлагают использовать так называемый контейнер (DI Container). Это разновидность шаблона Service Locator'а, где регистрируется связка КлассИнтерфейса=КонкретныйКласс. Обычно эти контейнеры настолько наворочены, что их выделяют в отдельные фреймворки и библиотеки. Собственно это то к чему я пришел убегая от сиглтона.

Почему в рунете так мало информации о паттерне Dependency Injection? А в англоязычных ресурсах информация представлена на 90% с примерами на Java. Я понимаю, что шаблон придумали именно разработчики Java, но неужели проблемы не существует на C++, что почти не реально найти примеры и классы? Что используете в разработке вместо Одиночек?

Автор: boostcoder 12.8.2010, 22:55
я бы предпочел синглтон. хоть это и антипаттерн, но иногда без него никуда.
второй вариант - передавать ссылку на Settings. чтоб не привязываться к типу, тип ссылки можно передать как шаблонный тип. ну или интерфейс(как вы и писали).

Автор: JackYF 12.8.2010, 23:14
Передаю, грубо говоря, shared_ptr< [const] Settings > в конструктор. Собственно, не очень понял, что такое Dependency Injection и чем оно лучше даже того же синглтона.

Автор: djamshud 12.8.2010, 23:32
Предложу анти в кубе не-паттерн: глобальный объект сеттингс. У вас (не теоретически, а практически!) может быть одновременно несколько наборов настроек, параллельно живущих и использующихся во время работы программы? Нет? Тогда плюньте на теоретика Александреску и решайте свою задачу, а не высосанную из пальца проблему.

Добавлено через 37 секунд
Впрочем и синглтон в равной степени подходит...

Автор: SABROG 13.8.2010, 00:38
Цитата(boostcoder @  12.8.2010,  22:55 Найти цитируемый пост)
я бы предпочел синглтон

Цитата(JackYF @  12.8.2010,  23:14 Найти цитируемый пост)
чем оно лучше даже того же синглтона.

Цитата(djamshud @  12.8.2010,  23:32 Найти цитируемый пост)
Впрочем и синглтон в равной степени подходит... 


Лучше тем, что не singleton с его недостатками. В принципе не сложно писать такие цепочки:

Код

int main(int argc, char* argv[])
{
    ServiceLocator loc;
    A a(&loc);
    return 0;
}
...
void A::A(ServiceLocator* loc)
{
    m_locator = loc;
}
...
void A::foo()
{
    C c = new C(m_locator);
}


Другое дело, что вариант не идеален,  а как работают DI Container'ы для C++ я пока не до конца разобрался.

Автор: Любитель 13.8.2010, 01:43
Во-первых, с иок-ом, как таковым, всё просто - не делай зависимости класса A от класса Settings. Делай интерфейс ISettings и работай с ним. 

Но, во-вторых, это неудобно - т. к. придётся передавать указатель на объект настроек. "Инъекция" возможна только в том случае, если все объекты мы получаем через DI-контейнер. Т. е. в итоге мы:
1. Регистрируем (где-нить при старте приложения) маппинг: ISettings -> DbSettings, или XmlSettings, или IniSettings (да, вообще логично что Settings - это чисто объект данных, а методы load/save/etc. в SettingsManager-е каком-нибудь, ну да ладно).
2. Добавляем параметр типа ISettings в конструктор нашего класса A (инъекция в конструктор - самая простейшая и достаточно популярная).
3. Вместо создания объекта класса A пишем container.Resolve<A>().

В общем-то всё красиво. У DbSettings у нас будет IDbConnection (я условно), который тоже автоматом зарезольвится на что надо - и т. д. Более того, любой DI-контейнер на практике действительно является и сервис локатором ("чистый" DI-контейнер должен производить только "инъекции", а создание/получение объектов - не его дело, но на практике такого не бывает). А это значит, что мы также можем регулировать время жизни объектов и пр.

Теперь проблемы. Любой настоящий DI-контейнер реализуется с использованием рефлекшена. Именно поэтому это (в основном) ява/шарп. В чистом С++ "правильного" DI вообще не может быть. С учёток оберток, предоставляющих метаданные (как в том ж Qt) - в принципе сделать вомзожно, но я не встречал готовых реализаций.

В случае С++ на мой взгляд есть 2 варианта:
1. Если нужна действительна абстракция - простейшая фабрика, без каких либо наворотов.
2. Если нет - то синглтон smile

Более того - даже в случае явы/шарпа я бы не стал городить иерархии классов/интерфейсов и полную абстракцию, если это бы действительно не было бы нужно.

Автор: SABROG 13.8.2010, 07:19
Цитата(Любитель @  13.8.2010,  01:43 Найти цитируемый пост)
С учёток оберток, предоставляющих метаданные (как в том ж Qt) - в принципе сделать вомзожно, но я не встречал готовых реализаций.

Они есть:

QNimbleContainer
QtIocContainer

Другое дело, что они жестко привязаны к мета-объектной системе и как следствие любой класс должен наследовать QObject. К тому же не хотелось бы использовать сторонние библиотеки/фреймворки ради того, чтобы просто уйти от синглтона.


Цитата(Любитель @  13.8.2010,  01:43 Найти цитируемый пост)
Если нужна действительна абстракция - простейшая фабрика, без каких либо наворотов.

То есть реализовать Service Locator через фабрику и передавать указатель на IServiceLocator через конструктор в каждый объект, типа http://www.rsdn.ru/forum/cpp/2458400.flat.aspx#2458400?
Цитата(Любитель @  13.8.2010,  01:43 Найти цитируемый пост)
Если нет - то синглтон  smile 

Эхх, если начать реализацию синглтона, то помимо идеологических проблем существует и физиологическая - проблема с потоко-безопасностью. Чтобы его сделать потоко-безопасным придется прибегнуть к double checked locking паттерну реализованному через атомарные операции, которых пока нет в C++, это значит надо использовать сторонние библиотеки, BOOST например (ну или начать использовать функции из C++0x)

Автор: Любитель 13.8.2010, 09:39
Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
QtIocContainer

Совсем не впечатлило. Всё делать через плагины - это жёстко.

Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
QNimbleContainer

Здесь уже гораздо лучше. Беглый просмотр блога говорит о том, что направление выбрано верное. Но.. насколько это production ready - уже другой вопрос (всё-таки пока похоже больше на грамотное увлечение).

Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
Другое дело, что они жестко привязаны к мета-объектной системе и как следствие любой класс должен наследовать QObject.

Ну.. так или иначе они будут расчитывать на некоторый базовый класс - для эмуляции рефлекшена (черех собственное хранилище метаданных). Ну или точнее так - в принципе базовый класс не обязательно, но на хранилище метаданных обязательно (ну плюс там ещё проблема универсальной передачи параметров появляется, решаемая в Qt через QVariant).

Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
То есть реализовать Service Locator через фабрику и передавать указатель на IServiceLocator через конструктор в каждый объект, типа этого?

Ну.. Service Locator реально нужен когда? Когда у нас накапливается много объектов, создаваемых через фабрики. И особенно, если при этом между ними есть какие-то связи. Т. е. по сути это централизованная фабрика. Т. е. в простых случаях достаточно обычных фабрик (per class). Далее - передавать указатель на IServiceLocator я бы не стал. Уж саму фабрику точно делал бы синглтоном, ну или просто статик поля/методы. Смысл абстрагировать абстракцию?

Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
Эхх, если начать реализацию синглтона, то помимо идеологических проблем существует и физиологическая - проблема с потоко-безопасностью.

Во-первых, в плане life scope в случае навороченных DI-контейнеров всегда есть опция синглетона. Наличие одного (и только одного) экземпляра класса в системе - не есть плохо. Плохо то, что мы жёстко зависимы от этого.

Во-вторых - будут ли проблемы с потоками, это зависит от конкретного кода объекта. И в большинстве случаев наиболее удачным решением будет просто использование TLS а не локи (по крайней мере уровень паралелизма будет гораздо выше). Любой уважающий себя компилятор это поддерживает, одним дефайном решается необходимость привязки к конкретному компилеру.

Автор: Earnest 13.8.2010, 10:11
Цитата(Любитель @  13.8.2010,  10:39 Найти цитируемый пост)
 И в большинстве случаев наиболее удачным решением будет просто использование TLS а не локи 

TLS - это хороший выход только если каждый поток должен иметь свой экземпляр. А это вроде бы не так. Если система не очень сложная, может, не стоит парится с thread-safe синглетона. Т.е. доступ к данным действительно нужно синхронизировать.
А "double checked locking паттерн" реально нужен только, если возможны накладки при создании или уничтожении.

Автор: Леопольд 13.8.2010, 10:20
Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
Чтобы его сделать потоко-безопасным придется прибегнуть к double checked locking паттерну реализованному через атомарные операции, которых пока нет в C++
Если я правильно понял настройки должны быть в одном инстансе, доступ к которому осуществляется из нескольких потоков. Как то можно гарантироваить безопастность доступа без какой-либо синхронизации? Ведь в любом случае придётся использовать какую-то блокировку, нет?

Автор: Леопольд 13.8.2010, 10:39
Цитата(Earnest @  13.8.2010,  10:11 Найти цитируемый пост)
А "double checked locking паттерн" реально нужен только, если возможны накладки при создании или уничтожении. 
Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
придется прибегнуть к double checked locking
Может просто создать объект в статической памяти?

Автор: Леопольд 13.8.2010, 11:22
Цитата(SABROG @  13.8.2010,  07:19 Найти цитируемый пост)
придется прибегнуть к double checked locking паттерну реализованному через атомарные операции, которых пока нет в C++
Код
#include <boost/thread/mutex.hpp>

template<typename T>
struct Singleton{
    static T& instance(){
        if(!instance_){
            boost::mutex::scoped_lock lock(mutex_);
            if(!instance_)
                instance_ = new T();
        }
        return const_cast<T&>(*instance_);
    }
private:
    static volatile T* instance_;
    static boost::mutex mutex_;
};
template<typename T>
volatile T* Singleton<T>::instance_ = 0;
template<typename T>
boost::mutex Singleton<T>::mutex_;

Не пойму, зачем здесь атомарные операции? Любой поток будет залочен до тех пор, пока объект не будет полностью сконструирован.

Автор: SABROG 13.8.2010, 11:37
Цитата(Любитель @  13.8.2010,  09:39 Найти цитируемый пост)
ну или просто статик поля/методы

Цитата(Леопольд @  13.8.2010,  10:39 Найти цитируемый пост)
Может просто создать объект в статической памяти?

QtCreator для кода:

Код

Core::EditorManager::instance();


Использует разновидность синглтона Мейерса. Только если в оригинале он не потоко-безопасен, то у них подход хитрее.
Предполагается, что любое приложение изначально создается как single threaded, поэтому пока мы не наплодили других потоков предварительно происходит инициализация всех "синглтонов", например где-нибудь в main(). Сам вариант приблизительно выглядит так:

Код

// .h
class EditorManager
{
public:
    EditorManager()
    {
         m_instance = this;
    }
    static EditorManager* instance() {return m_instance;}
private:
    static EditorManager* m_instance;
};
...
// .cpp
static EditorManager::m_instance = 0;
...
// main.cpp
int main(int argc, char* argv[])
{
    (void)new EditorManager; // создание экземпляра одиночки
}



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

    static GlobalClass *instance()
    {
        if (!s_instance)
          s_instance = new GlobalClass;
        return s_instance;
    }



Цитата(Любитель @  13.8.2010,  09:39 Найти цитируемый пост)
Смысл абстрагировать абстракцию?

Чтобы отвязаться от класса ServiceLocator, впихнуть тесты, при желании подсунуть "заглушку".

Цитата(Earnest @  13.8.2010,  10:11 Найти цитируемый пост)
Если система не очень сложная, может, не стоит парится с thread-safe синглетона. Т.е. доступ к данным действительно нужно синхронизировать.
А "double checked locking паттерн" реально нужен только, если возможны накладки при создании или уничтожении. 

Предположим я хочу научиться делать автомобили. Специалисты отправляют меня сначала научиться делать велосипеды. Когда я научусь их делать, то настанет очередь чего-то более сложного. То есть на данном этапе мне важно не быстрей что-либо написать, а понять как делать это правильно в будущем, когда такая необходимость настанет.

Цитата(Леопольд @  13.8.2010,  10:20 Найти цитируемый пост)
Как то можно гарантироваить безопастность доступа без какой-либо синхронизации? Ведь в любом случае придётся использовать какую-то блокировку, нет?

В случае заранее проинициализированного указателя проблем атомарного чтения самого указателя возникнуть не должно (по крайней мере на AI-32), а работу с данными на которые он указывает нужно синхронизировать, благо в Qt достаточно поток-безопасных классов и Qt сама берет на себя эту рутину. Тут конечно остается проблема с "висячим" объектом синглтона, который может быть даже и не используется нигде в коде (но это редкий случай, нафига его тогда вообще создавать).

Цитата(Леопольд @  13.8.2010,  11:22 Найти цитируемый пост)
Не пойму, зачем здесь атомарные операции? Любой поток будет залочен до тех пор, пока объект не будет полностью сконструирован.


Во первых мутекс блокирует работу всех остальных потоков, которые пытаются получить доступ к синглтону, а могли бы делать полезную работу. Если дальше копать, то там нужен второй мутекс, а потом в итоге и он не спасает, погугли на "Double Checked Locking Broken".

Как я уже упомянул нужен "безопасный" вариант шаблона Double Checked Locking, который реализован в функции boost::callonce(), вот пример нормального потоко-безопасного синглтона: http://www.boostcookbook.com/Recipe:/1235044

Но это мне не подходит, так как я пока не хочу тянуть boost, да еще и ради антипаттерна.

Автор: Любитель 13.8.2010, 11:48
Цитата(SABROG @  13.8.2010,  11:37 Найти цитируемый пост)
Недостатки такого подхода очевидны, но я пока склоняюсь к этому варианту, как наиболее простому и хоть частично потоко-безопасном по сравнению с таким вариантом, который без синхронизации потоков вообще не безопасен:

А, так ты ведёшь речь именно про создание инстанса. А я то думал про "небезпоасные" его методы. Если ты уверен в безопасности методов - то очевидно, что тут и обычные объекты синхронизации (т. е. обычная блокировка) подойдут:
Код

if (_instance)
  return _instance;

_mutex.lock();
if (!_instance)
  _instance = new MyClass;
_mutex.release();

return _instance;


Цитата(SABROG @  13.8.2010,  11:37 Найти цитируемый пост)
Чтобы отвязаться от класса ServiceLocator, впихнуть тесты, при желании подсунуть "заглушку".

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

Автор: SABROG 13.8.2010, 12:07
Цитата(Любитель @  13.8.2010,  11:48 Найти цитируемый пост)
(т. е. обычная блокировка) подойдут

Так это и есть тот самый "сломанный" вариант Double Checked Locking паттерна.

Цитата(Любитель @  13.8.2010,  11:48 Найти цитируемый пост)
Но зачем сервис-локатор абстрагировать? Единственный случай, который можно придумать - это желание использовать для него какой-т фреймворк и возможность переключение между разными фреймворками (не в процессе, а в перспективе проекта). Но.. это только если речь про достаточно нетривиальную реализацию.

Зачем-то разработчики Microsoft его используют (IServiceLocator + IServiceProvider). Видимо, чтобы конечный пользователь мог подставлять собственные фабрики. К тому же опять идет жесткая привязка к конкретной реализации:

user posted image

Автор: Леопольд 13.8.2010, 12:30
Цитата(SABROG @  13.8.2010,  11:37 Найти цитируемый пост)
Во первых мутекс блокирует работу всех остальных потоков, которые пытаются получить доступ к синглтону, а могли бы делать полезную работу. Если дальше копать, то там нужен второй мутекс, а потом в итоге и он не спасает, погугли на "Double Checked Locking Broken".
Блокирует только если инстанс не создан и до тех пор, пока он не будет создан целиком, иначе просто возвращает указатель без блокировки.

Автор: SABROG 13.8.2010, 12:45
Цитата(Леопольд @  13.8.2010,  12:30 Найти цитируемый пост)
Блокирует только если инстанс не создан и до тех пор, пока он не будет создан целиком, иначе просто возвращает указатель без блокировки.

Фиг с ней с этой блокировкой, там причина в многократной инициализации одного и того же синглтона в разных потоках.

Автор: Леопольд 13.8.2010, 13:23
Цитата(SABROG @  13.8.2010,  12:45 Найти цитируемый пост)
там причина в многократной инициализации одного и того же синглтона в разных потоках.
Цитата(SABROG @  13.8.2010,  12:45 Найти цитируемый пост)
Блокирует ... до тех пор, пока он не будет создан целиком


Автор: SABROG 13.8.2010, 13:31
Цитата(Леопольд @  13.8.2010,  13:23 Найти цитируемый пост)
Блокирует ... до тех пор, пока он не будет создан целиком

Это относится к одному потоку. Каждый поток будет создавать целиком по своей копии. На уровне компилятора тут нет синхронизации.

Не забывай и про такую ситуацию:

Код

m_instance = new MySingleton; 
// под объект выделяется память и указатель присваивается m_instance
// затем другой поток прерывает работу этого потока еще до того как он успеет вызвать конструктор
// другой поток видит, что указатель существует, т.е. объект создан и пытается с ним работать
// а объект синглтона не валиден по той простой причине, что конструктор еще не вызывался

Автор: boostcoder 13.8.2010, 13:34
Цитата(SABROG @  13.8.2010,  11:37 Найти цитируемый пост)
в функции boost::callonce()

Цитата(SABROG @  13.8.2010,  11:37 Найти цитируемый пост)
Но это мне не подходит, так как я пока не хочу тянуть boost, да еще и ради антипаттерна.

ради call_once() boost тащить не надо. она есть во всех свежих версиях компиляторов.

Добавлено через 1 минуту и 56 секунд
SABROG, мешанина какая-то в теме..
в данный момент, как я понял, проблема в инициализации синглтона? или в чем?

Добавлено через 2 минуты и 40 секунд
Цитата(boostcoder @  13.8.2010,  13:34 Найти цитируемый пост)
она есть во всех свежих версиях компиляторов.

по моему, она есть даже в Qt.

Автор: SABROG 13.8.2010, 13:46
Цитата(boostcoder @  13.8.2010,  13:34 Найти цитируемый пост)
она есть во всех свежих версиях компиляторов.

Она есть только в стандарте C++0x. Я конечно не консерватор, но стандарт еще не принят и хочется переносимости на старые компиляторы.

Цитата(boostcoder @  13.8.2010,  13:34 Найти цитируемый пост)
в данный момент, как я понял, проблема в инициализации синглтона? или в чем?

Сейчас мне интересно мнение насчет варианта без синглтона, варианта, который используется в нескольких проектах, которые я обнаружил через google/codesearch. То есть разновидность глобальных указателей:
Код

// .h
class EditorManager
{
public:
    EditorManager()
    {
         m_instance = this;
    }
    static EditorManager* instance() {return m_instance;}
private:
    static EditorManager* m_instance;
};
...
// .cpp
static EditorManager::m_instance = 0;
...
// main.cpp
int main(int argc, char* argv[])
{
    EditorManager em; // создание экземпляра одиночки
    doStuff();
    ...
}


Цитата(boostcoder @  13.8.2010,  13:34 Найти цитируемый пост)
по моему, она есть даже в Qt. 

Нету, я писал свою реализацию при помощи Дмитрия Вьюкова (специалиста по конкурентному программированию).

Автор: Леопольд 13.8.2010, 14:08
Цитата(SABROG @  13.8.2010,  13:31 Найти цитируемый пост)
// другой поток видит, что указатель существует, т.е. объект создан и пытается с ним работать
// а объект синглтона не валиден по той простой причине, что конструктор еще не вызывался
Спасибо. Упустил этот момент.

Добавлено @ 14:12
Цитата(SABROG @  13.8.2010,  13:46 Найти цитируемый пост)
Сейчас мне интересно мнение насчет варианта без синглтона, варианта, который используется в нескольких проектах, которые я обнаружил через google/codesearch. То есть разновидность глобальных указателей:
Хорошо сочетается с принципом http://en.wikipedia.org/wiki/KISS_Principle smile

Автор: mes 13.8.2010, 14:50
Цитата(SABROG @  12.8.2010,  20:44 Найти цитируемый пост)
. Напрашивается синглтон, но мы знаем, что это антипаттерн и поэтому он быстро "распрашивается" обратно

не читал, темы, но для доступа к настройкам имхо уже есть _синглетон_ приложения , который можно использовать. (наследник QApp в вашем случае ) smile

Автор: SABROG 13.8.2010, 16:20
Цитата(mes @  13.8.2010,  14:50 Найти цитируемый пост)
наследник QApp в вашем случае

Вопрос задавался в контексте языка C++, а не конкретного фреймворка. А так то варианты есть.

Автор: Леопольд 13.8.2010, 16:33
Цитата(SABROG @  13.8.2010,  13:31 Найти цитируемый пост)
// другой поток видит, что указатель существует, т.е. объект создан и пытается с ним работать
// а объект синглтона не валиден по той простой причине, что конструктор еще не вызывался
Код

#include <boost/thread/mutex.hpp>

template<typename T>
struct Singleton{
    static T& instance(){
        if(!instance_){
            boost::mutex::scoped_lock lock(mutex_);
            if(!instance_)
                instance_ = create_();
        }
        return const_cast<T&>(*instance_);
    }
private:
    static volatile T* instance_;
    static boost::mutex mutex_;

    typedef T* (*FPtr) ();
    typedef volatile FPtr VFPtr;
    static VFPtr create_;
    static T* creator(){
        return new T();
    }
};
template<typename T>
volatile T* Singleton<T>::instance_ = 0;
template<typename T>
boost::mutex Singleton<T>::mutex_;
template<typename T>
typename Singleton<T>::VFPtr Singleton<T>::create_ = &Singleton<T>::creator;
smile 

Автор: mes 13.8.2010, 17:32
Цитата(SABROG @  13.8.2010,  15:20 Найти цитируемый пост)
Вопрос задавался в контексте языка C++, а не конкретного фреймворка. А так то варианты есть. 

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

Автор: SABROG 13.8.2010, 18:25
Цитата(Леопольд @  13.8.2010,  16:33 Найти цитируемый пост)

Код

    static T* creator(){
        return new T();
    }


К сожалению не спасет от двух видов оптимизации. Одну делает компилятор, другую может делать линкер. Как получить гарантии того, что этих оптимизаций производиться не будет на всех известных компиляторах?

Как http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf бы Александреску с Мейерсом: "Game over. You lose." Почитай статью, чтобы не придумывать новых велосипедов.


Автор: Леопольд 13.8.2010, 18:38
Цитата(SABROG @  13.8.2010,  18:25 Найти цитируемый пост)
К сожалению не спасет от двух видов оптимизации. Одну делает компилятор, другую может делать линкер. Как получить гарантии того, что этих оптимизаций производиться не будет на всех известных компиляторах?
volatile указатель на функцию, и вызов функции через этот указатель.
Александреску конечно умный мужик, но это не повод унывать...  smile 

Код
    typedef T* (*FPtr) ();
    typedef volatile FPtr VFPtr;
    static VFPtr create_;
    ...
    template<typename T>
    typename Singleton<T>::VFPtr Singleton<T>::create_ = &Singleton<T>::creator;
    ...
    instance_ = create_();


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

Автор: Леопольд 13.8.2010, 18:55
Цитата(SABROG @  13.8.2010,  18:25 Найти цитируемый пост)
Почитай статью
Читал, где-то год назад. Вот ещё интересная статься под авторством Александреску. http://www.drdobbs.com/184403766;jsessionid=AMG55IDTCLHXLQE1GHPSKH4ATMY32JVN

Автор: SABROG 13.8.2010, 19:52
Следом идет другая проблема - когерентность кеша. У каждого ядра/процессора своя копия данных памяти в кеше и когда один поток меняет данные (устанавливает указатель, инициализирует члены класса), то нужно об этом сообщать другим потокам, иначе у них останутся "старые" данные. То есть возникает необходимость добавлять барьеры памяти или использовать атомарные инструкции типа Acquire/Release, чтобы сообщать другим потокам, что чего-то изменилось и кеши нужно обновить. Стандартных механизмов сделать это в C++ нет. На данном этапе этот язык совершенно не подготовлен к конкурентному программированию. К счатью работа в этом направлении ведется и с новым стандартом мы получим полноценный набор атомарных методов.

Автор: Леопольд 13.8.2010, 19:56
Цитата(SABROG @  13.8.2010,  19:52 Найти цитируемый пост)
К счатью работа в этом направлении ведется и с новым стандартом мы получим полноценный набор атомарных методов. 
Когда ещё это будет...

Автор: mes 13.8.2010, 20:27
Цитата(SABROG @  13.8.2010,  18:52 Найти цитируемый пост)
На данном этапе этот язык совершенно не подготовлен к конкурентному программированию

 smile smile

Автор: boostcoder 14.8.2010, 00:25
SABROG, ваша ситуация ясна.
но последующими вопросами, вы, похоже, пытаетесь всех загрузить? smile 
то, о чем вы мыслите - правильно. я бы сказал, через чур правильно. т.к. сложность на реализацию "правильного", переплевывает все адекватные времячасовые расходы.

Цитата(SABROG @  13.8.2010,  19:52 Найти цитируемый пост)
Следом идет другая проблема - когерентность кеша. У каждого ядра/процессора своя копия данных памяти в кеше и когда один поток меняет данные (устанавливает указатель, инициализирует члены класса), то нужно об этом сообщать другим потокам, иначе у них останутся "старые" данные.

в с++0х есть способы борьбы.

Цитата(SABROG @  13.8.2010,  19:52 Найти цитируемый пост)
Стандартных механизмов сделать это в C++ нет.

угу.

Цитата(SABROG @  13.8.2010,  19:52 Найти цитируемый пост)
На данном этапе этот язык совершенно не подготовлен к конкурентному программированию.

угу.
смотрите http://code.google.com/p/go/
Цитата(SABROG @  13.8.2010,  19:52 Найти цитируемый пост)
К счатью работа в этом направлении ведется и с новым стандартом мы получим полноценный набор атомарных методов.

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

Автор: SABROG 14.8.2010, 08:24
Цитата(boostcoder @  14.8.2010,  00:25 Найти цитируемый пост)
вы, похоже, пытаетесь всех загрузить?  smile 

Нет, просто объяснил свою позицию насчет синглтонов. Думаете мне хотелось вдаваться в детали проблемы? Это всего лишь ответы на ваши вопросы.

Цитата(boostcoder @  14.8.2010,  00:25 Найти цитируемый пост)
т.к. сложность на реализацию "правильного", переплевывает все адекватные времячасовые расходы.

Я думаю это тот случай, когда достаточно один раз сделать правильно и использовать в последующих проектах.

Цитата(boostcoder @  14.8.2010,  00:25 Найти цитируемый пост)
в с++0х есть способы борьбы.

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

Цитата(boostcoder @  14.8.2010,  00:25 Найти цитируемый пост)
угу. а вы только краски сгущаете. не забывайте о том, в каких годах, и для какого оборудования разрабатывался с++.

Самое смешное в том, что за последние 20 лет вышло такое количество разных языков программирования, где многих недостатков C++ просто нет. Когда у меня на компьютере по нескольку раз в неделю обновляется виртуальная машина Java я вижу насколько быстро этот язык развивается. Я не вижну какой-то активности по этому поводу со стороны разработчиков C++. А они вообще есть? Есть люди, которые пишут инструкцию (стандарт), а есть те, кто на основе неё пишет компилятор (ms, gcc, intel), и каждый делает это по своему разумению, да так, что производительность, устойчивость, переносимость программ с одним и тем же исходным кодом - разная.

Автор: Леопольд 14.8.2010, 08:48
Цитата(SABROG @  14.8.2010,  08:24 Найти цитируемый пост)
Когда у меня на компьютере по нескольку раз в неделю обновляется виртуальная машина Java я вижу насколько быстро этот язык развивается.
Может это они баги правят.... smile

Автор: SABROG 14.8.2010, 12:54
Почитал, что предложил JackYF, вроде тоже вариант не плохой. Как насчет такой реализации?

Код

class ISettings;
class ISettings
{
public:
    typedef QSharedPointer<ISettings> Ptr;
    ...
    virtual bool load() = 0;
    virtual bool save() = 0;
    virtual QVariant value(const QString& name) = 0;
};

class Settings : public ISettings
{
public:
    ...
    virtual bool load()
    {
        QFile file("settings.ini");
        ...
    }
    
    virtual QVariant value(const QString& name)
    {
        return data[name].value();
    }
};

class IDownloadManager
{
public:
    typedef QSharedPointer<IDownloadManager> Ptr;
    ...
    virtual bool connectToServer(const QString& host, const QString& port) = 0;
}

class DownloadManager : IDownloadManager
{
    ...
public:
    explicit DownloadManager(ISettings::Ptr settings) : m_settings(settings)
    {
    }

    ISettings::Ptr settings() const {return m_settings;}

    virtual bool connectToServer(const QString& host, const QString& port)
    {
        ...
    }

    void foo()
    {
        ...
        connectToServer(settings()->value("host").toString(), 
                        settings()->value("port").toString());
    }

private:
    ISettings::Ptr m_settings;
};

int main(int argc, char* argv[])
{
    ISettings::Ptr settings = ISettings::Ptr(new Settings);
    IDownloadManager::Ptr downloadManager 
                = IDownloadManager::Ptr(new DownloadManager(settings));
    ...
    return 0;
}



Конечно банальная передача указателя через конструктор, в дальнейшем пойдет через метод settings() по цепочке во внутренние объекты.
Плюсы в том, что умный указатель следит за жизнью объекта, его валидность почти всегда можно проверить. Класс ISettings позволяет разбить зависимость между двумя классами таким образом, что классы можно будет тестировать по отдельности не говоря о том, что можно подсовывать разные реализации этих настроек, например если они отличаются методом хранения (файл, база данных, сеть).

Автор: boostcoder 14.8.2010, 14:38
ИМХО, операторы new в main() лишние. и смартпоинтер в этом случае не нужен.

Автор: SABROG 14.8.2010, 14:41
Цитата(boostcoder @  14.8.2010,  14:38 Найти цитируемый пост)
ИМХО, операторы new в main() лишние. и смартпоинтер в этом случае не нужен. 

Как бы предполагается, что эта реализация может работать не только в main(), но и в каком нибудь MainWindow. Без new смарт поинтеры вроде как работать не могут. Все-таки удалять в деструкторе локальную переменную через delete - чревато.

Автор: boostcoder 14.8.2010, 15:09
Цитата(SABROG @  14.8.2010,  14:41 Найти цитируемый пост)
Без new смарт поинтеры вроде как работать не могут. Все-таки удалять в деструкторе локальную переменную через delete - чревато.

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

Автор: SABROG 14.8.2010, 15:31
Цитата(boostcoder @  14.8.2010,  15:09 Найти цитируемый пост)
они легко могут быть автоматическими переменными

Ситуации разные бывают, иногда необходимо освободить ресурс, если он никому не нужен. В случае с объектами созданными в main() этого освобождения не будет (как в коде, который я написал). Но если время жизни объекта, который проинициализировал другой объект, который должен жить пока нужен кому-то, меньше, то нет смысла занимать память двумя объектами сразу только потому, что объект является хозяином указателя/ссылки.

Автор: boostcoder 14.8.2010, 15:54
Цитата(SABROG @  14.8.2010,  15:31 Найти цитируемый пост)
Ситуации разные бывают

да. но вашим кодом, вы обязываете пользователя использовать new.

Автор: SABROG 14.8.2010, 17:07
Цитата(boostcoder @  14.8.2010,  15:54 Найти цитируемый пост)
да. но вашим кодом, вы обязываете пользователя использовать new.


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

Автор: SABROG 16.8.2010, 17:42
В общем как обычно, с этими интерфейсами больше проблем.

Код

class IDownloadManager
{
public:
    typedef QSharedPointer<IDownloadManager> Ptr;
    virtual ~IDownloadManager() {}
};
...
class DownloadManager : public QObject, public IDownloadManager
{
    Q_OBJECT
public:
    explicit DownloadManager(QObject *parent = 0);

signals:

public slots:

};


Далее в коде нужно передавать указатель типа QObject во внутренние классы Qt. И тут возникает проблема downcast'инга от класса интерфейса да DownloadManager'a. В общем каждая собачка - зверушка, но не каждая зверушка - собачка. Наследовать интерфейсы от QObject'a тоже как-то не хочется, как-то это не легковесно.

Автор: mes 17.8.2010, 09:58
Цитата(SABROG @  16.8.2010,  16:42 Найти цитируемый пост)
 передавать указатель типа QObject во внутренние классы Qt

Цитата(SABROG @  16.8.2010,  16:42 Найти цитируемый пост)
от класса интерфейса 

все зависит от того, что должен выражать интерфейс.. 


Автор: SABROG 17.8.2010, 10:34
Цитата(mes @  17.8.2010,  09:58 Найти цитируемый пост)
все зависит от того, что должен выражать интерфейс.. 

Да я и сам не знаю smile Хотелось реализовать вариант dependency injection в противовес одиночке. Пока получаю один геморрой. Уже неделю не могу начать писать хоть что-то, всё архитектуру придумываю и каждый раз её приходиться заново переписывать.

Автор: Леопольд 17.8.2010, 10:35
SABROG, не пойму, чем это лучше обычного синглтона... К тому же, если скорость критична (не просто так ведь заботишься о расходах на синхронизацию кэша ядер?), то виртуальные вызовы не лучший вариант.

Если не обойтись без downcasting'а, но dynamic_cast недопустим (в релизе), то можно использовать http://forum.sources.ru/index.php?showtopic=296715&st=15... Я его выдернул из книги Александреску и Саттера "Стандарты программирования на С++"
Хотя, IMHO лучше всё же обойтись...

Автор: SABROG 17.8.2010, 15:03
Цитата(Леопольд @  17.8.2010,  10:35 Найти цитируемый пост)
то виртуальные вызовы не лучший вариант.

А разве есть какой-нибудь оверхед, если метод интерфейса чисто виртуальный?

Код

virtual void foo() = 0;

Автор: mes 17.8.2010, 18:13
Цитата(SABROG @  17.8.2010,  14:03 Найти цитируемый пост)
А разве есть какой-нибудь оверхед, если метод интерфейса чисто виртуальный?

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

и еще не понял, какая связь между синглетоном и  Dependency Injection в рамках этой темы ? 
одно не альтернатива другому, а дополнение.

Автор: SABROG 17.8.2010, 22:32
Цитата(mes @  17.8.2010,  18:13 Найти цитируемый пост)
и еще не понял, какая связь между синглетоном и  Dependency Injection в рамках этой темы ? 
одно не альтернатива другому, а дополнение.

Разве? Разница такая вроде бы:

// Dependency Injection
Код

MyClass::MyClass(Settings* settings)
{
    OtherClass* oc = new OtherClass(settings);
    int size = settings->value("size").toInt();
}

OtherClass::OtherClass(Settings* settings)
{
    std::string str = settings->value("user").toString();
}


// Singleton

Код

MyClass::MyClass()
{
    int size = Settings::instance()->value("size").toInt();
}

OtherClass::OtherClass()
{
    std::string str = Settings::instance()->value("user").toString();
}

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)