LINUX.ORG.RU

IoC в С++

 ,


1

6

.. не Dependency Injection, а именно Inversion of Control я смотрю в плюсах не популярен. В мире Java без этого тебя сначала засмеют, а потом когда поймут что ты не унимаешься - то изобьют.

Что я подразумеваю под IoC

  • Есть контейнер, в которым по определенным ключам (типы, строки) регистрируются классы
  • Контейнер обязан уметь создавать инстансы каждого из классов, если надо подставляя ему или в конструктор или как-то по другому его зависимости
  • Группы регистраций классов можно обьединять в модули, которые просто устанавливаются подключением к основном контейнеру. Они тогда предоставлют или требуют другие класса для своей работы. По сути как паззл.
  • В тестах можно заменять целы модули указывая «запусти мне весь контейнер, но пожалуйста замени MyClass на MyMockClass»

Пример на псевдокоде который вроде бы как С++

class IB {
public:
  int value() = 0;
};

class B : public IB {
public:
   int value() {
      return 1;
   }
};

class A {
   A(std::shared_ptr<IB> b) : b_(b) {}

   int value() {
     return b_->value();
   }
private:
   std::shared_ptr<IB> b_;
};

void MyModule(Container& c) {
 c.RegisterAs<IB, B>(CREATE(
   B()
 ));

 c.Register<A>(CREATE(
   A(INJECT(IB))
 ));
}

int main() {
 Container c;
 MyModule(c);
 std::cout << c.Get<A>()->value() << std::endl;
 return 0;
}

Вместо shared_ptr может вполне быть unique_ptr и B будет не синглтоном внутри контейнера, а будет создаваться отдельно для каждого класса-пользователя. Слово синглтон перестает быть пугающим, потому что это не глобальный синглтон, а синглтон в одном контейнере, плюс легко тестируется и нету проблем с неправильной инициализацией.

В тесте запросто выполняется MyModule, а потом регистрация IB меняется на MockB.

Примеры существующих фреймворков

https://github.com/google/fruit

https://github.com/ybainier/Hypodermic

Вопрос, чего не популярно? Врядли аргументы оправданы о том что это лишнее и все такое актуальны, пакетные менеджеры это решают. Зато тестирование на уровень легче, что уже с десяток лет используется в Java во все поля

★★★★★

Ответ на: комментарий от vertexua

Ну вот люди предлагают вариант с плагинами. Более чем реюзабельно.

Gorthauer ★★★★★
()

Потому что IoC - это паттерн вышедший из Java. Чтобы использовать IoC в C++, нужно писать код как на Java, т.е. с иерархиями интерфейсов и классов, наследованием, виртуальными функциями, динамическим выделением объектов и т.д. Может быть такой стиль программирования и был популярен в C++ в 90-х годах, но сейчас он является уже далеко не идиоматическим. Идиоматическое-же программирование на C++ с использованием value-semantic классов, отсутствие наследование, использованием шаблонов и параметрического полиморфизма не совместимо с IoC паттерном.

rupert ★★★★★
()
Ответ на: комментарий от Gvidon

Легко. Зависимости могут быть опциональными. ... но у тебя вдруг новые зависимости.

Еслиб только ты знал что не могут, что зависимости декларируются для типа а не объекта, но видимо это что-то сложное 8)

Deleted
()
Ответ на: комментарий от rupert

Чтобы использовать IoC в C++, нужно писать код как на Java, т.е. с иерархиями интерфейсов и классов, наследованием, виртуальными функциями, динамическим выделением объектов и т.д.

Достаточно reflection, в c++ уже подвезли?

Deleted
()
Ответ на: комментарий от Gvidon

И где ты возьмёшь эту информацию для типа? У тебя даже библиотека, в которой он определён, не загружена.

Понимаешь если информацию для типа негде взять - то не о чем и разговаривать, т.е. ты мне тут часами пудрил мозги.

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

Deleted
()
Ответ на: комментарий от Deleted

Достаточно reflection, в c++ уже подвезли?

Достаточно для реализации в одном из видов, а не обоснования необходимости.

anonymous
()
Ответ на: комментарий от Deleted

проверить граф зависимостей в момент загрузки оной.

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

Gvidon ★★★★
()
Ответ на: комментарий от anonymous

обоснования необходимости.

Как это фичи языка могут обосновывать необходимость каких либо костылей?

Deleted
()
Ответ на: комментарий от Gvidon

Эта проблема просто не решается в общем случае для плюсов

Поэтому в плюсах ее и не решают, а пишут код с детерминистичными зависимостями

annulen ★★★★★
()
Ответ на: комментарий от Gvidon

Вот мы и пришли к тому, что граф придётся перепроверять в рантайме на каждый чих.

Вы там когда находитесь, сядьте и напишите мне почему «загрузка модуля» == «каждый чих». А пока ходите не стоит писать бред, выглядит уныло.

Deleted
()

Я вообще не понимаю, что тут обсуждают. Какие-то графы зависимостей, которые надо проверять на каждый чих. Вы 100500 раз в секунду что-ли чихаете? Ну обошел ты этот граф с 20-200 узлов при загрузке очередного модуля или возникновении зависимости, и чего? Если у вас hft или хайлод, то причем тут ява вообще?

На ворчание можно не отвечать, просто скажите, я один считаю это нереальным бредом? А то самооценка падает.

arturpub ★★
()
Ответ на: комментарий от Deleted

Не могу до сих пор понять початему если речь идет о загружаемых и выгружаемых библиотеках (чтобы сэкономить 300 КБ памяти на 16ГБ машина), то нельзя для них просто сделать отдельные контейнеры, а потом связать через понятный класс, раз нету доверия контейнеру или нету желания загрязнять регистрациями главный контейнер

vertexua ★★★★★
() автор топика
Ответ на: комментарий от arturpub

Я вообще не понимаю,

Рассказываю: «адепты» с++ хелловорлдов рассказывают java-ынтерпразщикам что вся ооп-шная мишура тормозит и нинужна.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от arturpub

Люди путают гипотетически хитро сделанный острел ног, который они и без контейнера создадут, с удобством написания большинства приложений. «Надо чистить зубы - Ха, а че если я щётку в жопе забуду!» Нормальные люди ищут где применить идею, ненормальные фокусируются как сделать так, чтобы она провалилась

vertexua ★★★★★
() автор топика
Ответ на: комментарий от vertexua

Строго говоря адекватный «IoC» контейнер должен позволять втыкать всякий мусор не заморачиваясь написанием адаптеров, иначе IRL с ним работать будет невозможно. Но тут речь про то что человек грузит «на каждый чих» (это примерно 100ms) библиотеку, я вот только не могу понять одну и туже или каждый раз новую.

Deleted
()
Ответ на: комментарий от Deleted

Я так понимаю что речь о плагинах, как движки MySQL или разные кодеки видеоплеера. М причём обязательно загружать и выгружать при кликании на галочке включения и выключения

vertexua ★★★★★
() автор топика
Ответ на: комментарий от Deleted

«адепты» с++ хелловорлдов

По секрету, в твоем браузере почти наверняка есть результат работы одного из «адептов».

рассказывают java-ынтерпразщикам

Удел большинства «java-ынтерпразщиков» - писать различные морды и прокладки для хранения данных в СУБД. Не так круто звучит, если расшифровать, верно?

anonymous
()
Ответ на: комментарий от vertexua

выгружать

Боюсь ничего не получится, даже в java не осилили. Вот в C# вроде что-то было для этих целей.

Deleted
()
Ответ на: комментарий от anonymous

По секрету, в твоем браузере почти наверняка есть результат работы одного из «адептов».

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

Удел большинства «java-ынтерпразщиков»

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

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от vertexua

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

Циклическая зависимость решается или третьим классом или вовсе не решается и один из классов должен быть готов работать с weak_ptr

vertexua ★★★★★
() автор топика
Ответ на: комментарий от vertexua

с удобством написания большинства приложений
Нормальные люди ищут где применить идею

Я тебя прямо спрашивал, я пишу СУБД (что так и есть), ни разу не было никакой необходимости в IoC, хотя интерфейсы и фабрики нашли применения. Посоветуй - где мне его приткнуть?

anonymous
()
Ответ на: комментарий от Deleted

Еслиб там было это то браузер бы падал гораздо чаще,

Речь идет о патчах с фиксами для одной базовой библиотеки. Так что как раз наоборот.

anonymous
()
Ответ на: комментарий от anonymous

В контейнерах нету необходимости (как и в том чтобы ты писал СУБД). Есть удобство. Если ты пытаешься доказать что с тупым топором и силой можно любое дерево срубить, то технически ты прав. Поздравляю с победой в споре

vertexua ★★★★★
() автор топика
Ответ на: комментарий от anonymous

В субд, IoC не нужен, а во встраиваемой вообще противопоказан.

Deleted
()
Ответ на: комментарий от vertexua

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

Т.е. ты сам не знаешь, где их можно попробовать приткнуть? Я тебя серьезно спрашиваю, где они будут удобны и принесут пользу.

Поздравляю с победой в споре

Ты лучше ответь на вопрос, если можешь.

anonymous
()
Ответ на: комментарий от anonymous

В любом приложении с двумя классами можешь воткнуть их в контейнер и плохо не будет. Представь если бы такой контейнер был в STL. Все бы просто по дефолту начинали писать начиная с создания контейнера, потому что при росте приложения пригодится, а особенно ничего не теряешь. Например сразу забросить туда модуль логгирования, чтобы нормально это инжектить логгер куда надо

vertexua ★★★★★
() автор топика

ммм... я тоже люблю обмазаться свежим говнецом.

anonymous
()
Ответ на: комментарий от vertexua

В любом приложении с двумя классами можешь воткнуть их в контейнер и плохо не будет

Плохо будет, т.к. будет больше кода. Вот ты сам привел пример, где у тебя есть полезный код - «return 1». А сколько вокруг этого кода ты добавил абстракций? А сколько ты добавил оверхеда? Да, я понимаю, что ты имел ввиду полезное применение, где более сложный случай, но когда это добавляется по принципу «плохо не будет» - это не вариант, особенно для С++.

Представь если бы такой контейнер был в STL. Все бы просто по дефолту начинали писать начиная с создания контейнера

Выше уже писали что то, что в Java ООП во многом взяли с С++, не означает, что в С++ все хотят писать в том стиле, к которому пришли в Java. C++ во-первых значительно ближе к С, во-вторых он не ограничивается только ООП,

потому что при росте приложения пригодится

А чо бл%, если нет? (с) не принимать за ругательство.

П.С. извини за грубый тон разговора ранее, был неправ

anonymous
()
Ответ на: комментарий от anonymous

В плане роста кода обычно сам полезный код значительно больше клея. Если речь о памяти - это просто unordered_map. Если речь о CPU, то это всего набор лукапов по одной unordered_map и может быть разовый алгоритм на графе поиска циклов за O(n). Если речь о сферической сложности добавления библиотеки, то есть пакетный менеджер и cmake, вам что, либу впервой добавлять

vertexua ★★★★★
() автор топика

Мне кажется, или жабо-программисты пытаются все сильно переусложнять и запутывать?

invy ★★★★★
()
Ответ на: комментарий от vertexua

В плане роста кода обычно сам полезный код значительно больше клея.

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

Если речь о памяти - это просто unordered_map.

Память - это shared_ptr<IB> в каждом экземпляре.

Если речь о CPU, то это всего набор лукапов по одной unordered_map и может быть разовый алгоритм на графе поиска циклов за O(n).

Нет, не здесь узкое место. Вот как написал бы человек без «потом пригодится», и вот какая разница в скорости:

~$ cat ./test1.cpp 
#include <iostream>
#include <memory>
using namespace std;

class A {
public:    
   int value() {
     return 1;
   }
};


int main( int argc, char** ) {
    A a;
    
    int s = 0;
    for( int i = 0 ; i < 100000000 * argc ; ++i )
        s += a.value();
    
    cout << s << endl;
}
~$ clang++ -Ofast -std=c++11 ./test1.cpp
~$ time ./a.out 
100000000

real	0m0.002s
user	0m0.000s
sys	0m0.000s
~$ 
~$ 
~$ cat ./test2.cpp 
#include <iostream>
#include <memory>
using namespace std;

class IB {
public:
  virtual int value() = 0;
};

class B : public IB {
public:
   int value() {
      return 1;
   }
};

class A {
public:    
   A(shared_ptr<IB> b) : b_(b) {}

   int value() {
     return b_->value();
   }
private:
   shared_ptr<IB> b_;
};


int main( int argc, char** ) {
    A a( make_shared<B>() );
    
    int s = 0;
    for( int i = 0 ; i < 100000000 * argc ; ++i )
        s += a.value();
    
    cout << s << endl;
}
~$ clang++ -Ofast -std=c++11 ./test2.cpp
~$ time ./a.out 
100000000

real	0m0.235s
user	0m0.232s
sys	0m0.000s

Разница в более чем 100 раз на самом обычном геттере.

anonymous
()
Ответ на: комментарий от anonymous

Разница в более чем 100 раз на самом обычном геттере.

Да, чтоб не было претензий по поводу разного кол-ва классов:

~$ cat ./test1.cpp
#include <iostream>
#include <memory>
using namespace std;

class IB {
public:
  virtual int value() = 0;
};

class B {
public:
   int value() {
      return 1;
   }
};

class A {
public:    
   int value() {
     return b.value();
   }
   
   B b;
};


int main( int argc, char** ) {
    A a;
    
    int s = 0;
    for( int i = 0 ; i < 100000000 * argc ; ++i )
        s += a.value();
    
    cout << s << endl;
}
~$ clang++ -Ofast -std=c++11 ./test1.cpp
~$ time ./a.out 
100000000

real	0m0.002s
user	0m0.000s
sys	0m0.000s

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

anonymous
()
Ответ на: комментарий от anonymous

Причем если в этой вашей JVM JIT-компилятор может убрать эту лишнюю динамику в процессе работы приложения, то в С++ это уже невозможно.

annulen ★★★★★
()
Ответ на: комментарий от anonymous

Синтетические тесты прекрасны. Выхлоп на ассемблере смотрели? Ну славно в первом случае компилятор свернул все в константу 10000000 и вывел на экран. Очень быстро, но увы не как в реальном приложении. Он такие трюки редко умеет.

Во втором случае было честное вычисление. Кстати, если вы замените shared_ptr на бесплатный unique_ptr или вообще B*, то скорость выполнения не меняется.

Что мы тестируем сдесь? Микрооптимизацию на hello world? Или код человека разбивающего голову о применение virtual для математического алгоритма?

Обычно сначала пишется корректный код с тестами. Потом его профилируют. Потом если надо выпиливают virtual в строго нужных методах и что-то придумывают с тестами другое. По лучше оставить в покое код, который вызывается 10 раз в секунду и написать для него нормальные тесты. У меня на проекте уйма кода, который вызывает функцию раз в минуту, его бы неплохо покрыть тестами. Любые new или манипуляции со строками будут в разы более крупным bottleneck чем virtual. Всякие сценарии с сотнями миллионамов вызовов цикла в секунду очень нечасты и разобьются вдребезги под любым реальным алгоритмом с STL структурами данных внутри тела цикла.

vertexua ★★★★★
() автор топика
Ответ на: комментарий от Deleted

Ты еще тут не рассказывал про JAVA-«кэшфрендлинесс» и JAVA-«локальность», бггг, «ынтерпразщик» :) В hft эту твою ооп-мишуру вырезают в первую очередь... тупым ржавым ножом — всем кто пытается ее писать :)

anonymous
()
Ответ на: комментарий от anonymous

В hft на Java просто используют очень простые и заранее аллоцированые структуры данных. В LMAX о их методах много писали.

vertexua ★★★★★
() автор топика
Ответ на: комментарий от vertexua

очень простые и заранее аллоцированые структуры данных

И? Где противоречие с «ооп-мишура нинужна и тормозит»?

заранее аллоцированые структуры данных

Потому что массивы ссылок на объекты не то же самое что массивы объектов :)

«LMAX aims to be the fastest trading platform in the world.» (ТМ) Ну, успехов им, чо. Целый новый «паттерн» изобрели для идиоматических приемов... из других языков и торговых платформ на них. Кольцевой буфер изобрели — аж Фоулер статью накатал на радостях :)

«Firstly, it's faster than something like a linked list because it's an array, and has a predictable pattern of access.» (с) Вот же блин, какое бесстыдное познавание мира перед ликом кэпа! Прям благая весть, программистишэ радуйсо! Баптисты джавы, свидетели Фоулера, озарились светом истины! Оказывается, массив лучше связанного списка! А мужыки-то не знали :)

anonymous
()
Ответ на: комментарий от vertexua

Выхлоп на ассемблере смотрели? Ну славно в первом случае компилятор свернул все в константу 10000000 и вывел на экран. Очень быстро, но увы не как в реальном приложении. Он такие трюки редко умеет.

Да, но решил, что и так прокатит ^_^. Ведь все честно в том плане, что компилятор через виртуальный вызов из под члена класса смог достать значение напрямую и это дало ему бонус. Точно так же он для реального кода вытянет константу из геттера. А насчет «редко»:

About 50% of virtual calls in Firefox are now speculatively devirtualized during link-time optimization.

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

Что мы тестируем сдесь? Микрооптимизацию на hello world? Или код человека разбивающего голову о применение virtual для математического алгоритма?

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

Обычно сначала пишется корректный код с тестами. Потом его профилируют.

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

Любые new или манипуляции со строками будут в разы более крупным bottleneck чем virtual. Всякие сценарии с сотнями миллионамов вызовов цикла в секунду очень нечасты и разобьются вдребезги под любым реальным алгоритмом с STL структурами данных внутри тела цикла.

Скорость работы программы - скорость работы всех компонентов. Если один бесполезный virtual call вместо заинлайненного кода сам по себе съест не так много как один бесполезный new, это не значит, что в итоге мы не потеряем ничего.

anonymous
()

Вопрос, чего не популярно?

1. В плюсах, мягко говоря, не злоупотребляют ключевым словом virtual. C++11 добавил ещё одну причину: standard layout.

2. В плюсах не злоупотребляют строительством разлапистых иерархий.

3. В плюсах нет рефлексии (но обещают), а значит придётся использовать шаблонное метапрограммирование, которым тоже стараются не злоупотреблять без нужды.

4. IoC это прежде всего идиома интерфейс-реализация. Главная её проблема в плюсах — отсутствие стандартного динамического линкера и метаданных о структуре модуля, которые можно использовать в рантайме (последнее обещают добавить).

5. В C++ не злоупотребляют (а иногда вообще выключают) исключениями. Без этого сложно сделать IoC.

Короче говоря: сложно, затратно и неинтероперабельно.

Macil ★★★★★
()
Ответ на: комментарий от rupert

rupert

Может быть такой стиль программирования и был популярен в C++ в 90-х годах, но сейчас он является уже далеко не идиоматическим. Идиоматическое-же программирование на C++ с использованием value-semantic классов, отсутствие наследование, использованием шаблонов и параметрического полиморфизма не совместимо с IoC паттерном.

Macil

Короче говоря: сложно, затратно и неинтероперабельно.

Пасоны, но вас послушать - так в крестах сейчас вообще ООП никому не нужно. Это правда?

ovk48 ★★★
()
Ответ на: комментарий от no-such-file

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

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

anonymous
()
Ответ на: комментарий от ovk48

Можно, конечно, применять ООП там, где оно подходит. Но тотальное ООП как в Java точно не нужно в C++. Нужно понимать, что там где на Java используется ООП, C++ предоставляет возможность использовать и другие парадигмы программирования, которые могут быть более применимы.

rupert ★★★★★
()

с использованием шаблонов ваш код сокращается до:

template<class T>
class A {
   A(std::shared_ptr<T> b) : b_(b) {}

   int value() {
     return b_->value();
   }
private:
   std::shared_ptr<T> b_;
};

int main() {
 Container c;
 c.register<A, B>();
 std::cout << c.Get<A>()->value() << std::endl;
 return 0;
}

разве нет?

anonymous
()
Ответ на: комментарий от anonymous

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

Ынтерпразщик, перелогинься :)

anonymous
()
Ответ на: комментарий от Iron_Bug

наоборот, высокая: написание кода сознательно, с пониманием всех тонкостей и оптимизационных задач, а не в беспамятстве нашлёпывание говна в фреймворке

Смотрю я на кеды та думку гадаю.... Но это все фигня если вдруг оказывается что культура написания от языка не зависит.

Deleted
()
Ответ на: комментарий от anonymous

Более того, можно сказать, что каждый объект типа контейнер работает только с одним классом. тогда получаем:

int main() {
 Container<A, B> c;
 std::cout << c.get()->value() << std::endl;
 return 0;
}

Итого, мы получили в результате просто произвольный шаблон. Вывод: ваш джавовский IoC — просто костыль для выполнения тех задач, которые в С++ решаются шаблонами.

anonymous
()
Ответ на: комментарий от anonymous

разве нет?

Неа. Там вся соль в том, что конфигурацию MyModule в Java любят выносить в конфиги (ога, те самые XML). Соотвт. по конфигам они конфигурируют приложение на стадии запуска, без перекомпиляции. С шаблонами у тебя так не прокатит, т.к. нужна честная подмена реализации IB в рантайме.

Norgat ★★★★★
()
Ответ на: комментарий от vertexua

Мда

Тут есть два момента: 1) да, TDD не пользуется у нас всеобщей популярностью; 2) никто не будет жертововать скоростью работы основного кода ради упрощения тестов, так что подстраиваться придется тестам

annulen ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.