ХГД>>Ага, одна пара маллок/фри на всю либу. То есть глобальные. Последовательного применения dependency injection, шоп например в каждый объект тащить аллокатор, логгер и конфиг, я в реальном вменяемом коде ни разу пока не видел. Не, я допускаю, что области где они сплошь требуются, бывают, но советовать это по умолчанию по-моему перебор.
A>ну вот есть 7zip, там так и сделано.
В котором месте? Чето не припомню там ни стороннего аллокатора, ни тем более логгера. Хотя давно я туда не лазил, мог и забыть.
A>если бы оттуда COM выпилить — был бы совсем хороший код %)
COMа там нет вообще, просто чувак писал в стиле как будто ком есть
А вообще за такой хороший код руки отрывать, диагностика на 3 с минусом. Ежели какая ошибка случилась — в вызывающем коде причину фиг узнаешь.
A>а вообще если использовать что-то типа DI-контейнера/сервис-локатора, то код получается довольно простым. A>
A>using MyDIContainer = std::tuple<Logger*, Config*, ...>;
A>class Foo {
A>public:
A> Foo(const MyDIContainer& di_container) {
A> auto cfg = get<Config*>(di_container); // std::get
A> ...
A> log(di_container, "something", 123); // calls "if (auto logger = get<Logger*>(di_container)) logger->log(args...);"
A> ...
A> auto bar = allocate<Bar>(di_container, 456); // calls "get<Allocator*>(di_container)->Create<T>(args...);"
A> };
A>};
A>
A>если сделать вспомогательный базовый класс, часть бойлерплейта можно убрать.
В подавляющем большинстве случаев более одного логгера никогда не потребуется. Т.е., весь этот код окажется написан зря.
ХГД>>Чтобы с этим не было проблем, достаточно обеспечить подключаемые реализации глобальных объектов с разумными умолчаниями. Например, логгер никуда не выводит, аллокатор вызывает CRT'шные malloc/free, конфиг возвращает какие-то дефолты. А DI уместнее оставить для случаев, где неявных границ нет, а разное поведение требуется.
A>Всё это хорошо до тех пор, пока тебе не понадобится например запустить две функции параллельно, и дать каждой свой логгер.
Вот ни разу такого изврата не требовалось. И нафига, спрашивается, я буду платить за все это ненужное счастье с самого начала?
A>Или вызвать void foo(function<void()> delegate); так чтобы код в foo писал в логгер, а delegate туда не писал.
Вот когда мне такое потребуется, я приделаю к этой конкретной функции параметр, писать в логгер или нет. И это будет на порядок проще и потребует на 2 порядка меньше работы, чем DI заранее сверху донизу.
A>Можно конечно запилить демультиплексор, но все же лучше иметь возможность задавать свои логи.
Чем лучше-то? Если я ей пользоваться, скорее всего, ни разу не буду?
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[6]: Как наиболее корректно работать с глобальными данными
Здравствуйте, Хон Гиль Дон, Вы писали:
ХГД>>>Ага, одна пара маллок/фри на всю либу. То есть глобальные. Последовательного применения dependency injection, шоп например в каждый объект тащить аллокатор, логгер и конфиг, я в реальном вменяемом коде ни разу пока не видел. Не, я допускаю, что области где они сплошь требуются, бывают, но советовать это по умолчанию по-моему перебор.
A>>ну вот есть 7zip, там так и сделано.
ХГД>>>>Ага, одна пара маллок/фри на всю либу. То есть глобальные. Последовательного применения dependency injection, шоп например в каждый объект тащить аллокатор, логгер и конфиг, я в реальном вменяемом коде ни разу пока не видел. Не, я допускаю, что области где они сплошь требуются, бывают, но советовать это по умолчанию по-моему перебор.
A>>>ну вот есть 7zip, там так и сделано.
BZ>typedef struct BZ>{ BZ> void *(*Alloc)(void *p, size_t size); BZ> void (*Free)(void *p, void *address); /* address can be 0 */ BZ>} ISzAlloc;
ISzAlloc упоминается в исходниках аж 42 раза. new употребляется более 300 раз. 42 раза, конечно, больше чем ничего, но на повсеместное использование не тянет. Т.е., свой аллокатор там, судя по всему, в тех местах, где автор подозревал влияние его на производительность. Что несколько отличается от применения DI по умолчанию по всему коду.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[8]: Как наиболее корректно работать с глобальными данными
Здравствуйте, Хон Гиль Дон, Вы писали:
ХГД>ISzAlloc упоминается в исходниках аж 42 раза. new употребляется более 300 раз. 42 раза, конечно, больше чем ничего, но на повсеместное использование не тянет. Т.е., свой аллокатор там, судя по всему, в тех местах, где автор подозревал влияние его на производительность. Что несколько отличается от применения DI по умолчанию по всему коду.
сравнивать new надо с Alloc, а не названием типа. для интереса поискал "->Alloc(" в исходниках 11-го года, там оно втречается раз 20, в алгоритмах сжатия и при разборе оглавления архива — т.е. только в тех местах, где могут потребоваться очень большие буфера и потому хитрая стратегия переиспользования памяти
Люди, я люблю вас! Будьте бдительны!!!
Re[2]: Как наиболее корректно работать с глобальными данными
K>Или можно в файле, где определен Log, для удобства сделать дефайн типа:
K>#define LOG *Log::instance()
Google C++ Style Guide говорит что это плохо:
Instead of using a macro to conditionally compile code ... well, don't do that at all (except, of course, for the #define guards to prevent double inclusion of header files). It makes testing much more difficult.
Здравствуйте, lnkuser, Вы писали:
L>Здравствуйте, koenjihyakkei, Вы писали:
K>>Или можно в файле, где определен Log, для удобства сделать дефайн типа:
K>>#define LOG *Log::instance()
L>Google C++ Style Guide говорит что это плохо:
L>Instead of using a macro to conditionally compile code ... well, don't do that at all (except, of course, for the #define guards to prevent double inclusion of header files). It makes testing much more difficult.
В этой цитате речь идёт совсем о другом: «conditionally compile code» — это про #if, #ifdef и прочее. В вышеприведённом макросе никаких условий нет.
Что, впрочем, не делает более привлекательной саму спорную идею заворачивания вызова в макрос.
Re[5]: Как наиболее корректно работать с глобальными данными
Здравствуйте, Abyx, Вы писали:
A>Здравствуйте, Шахтер, Вы писали:
Ш>>>>Это глупость. Например, хип есть глобальный объект и ты им (как правило неявно) пользуешься в большинстве программ. A>>>во многих библиотеках делают возможность задать свои функции аллокации/деаллокации, и хип напрямую не используется.
Ш>>И что, хип после этого перестаёт быть глобальным объектом?
A>да, перестает.
Нет. Двойка.
A>в той же винде можно создать несколько разных хипов для разных потоков или модулей программы. A>или например можно использовать arena allocator'ы.
L>Допустим есть класс Log, который инициализируется при старте программы гдето в самом начале main(). L>И есть другие части программы которые работают в разных потоках, там тоже используется Log.
Что касается лога, то я обычно инициализирую экземпляр логгера на каждый файл с исходным кодом. Пишу где-нибудь в самом начале:
Отвечу в двух словах:
L>Везде, абсолютно везде пишут что глобальные переменные, объекты это плохо и очень плохо.
Врут. Нагло и беспардонно врут. Глобальные переменные -- это не плохо или хорошо, а просто глобальные
переменные, у них есть свои особенности, их надо знать, и уметь ими пользоваться.
L>Но нигде нормального ответа как обходится без них я не нашел. На форуме искал.
Без них ненадо обходиться. Их надо использовать, но использовать их надо правильно.
В частности, если испольуешь их в разных потоках, доступ к ним нужно защищать.
Если тебе нужны "как бы глобальные" переменные, но для отдельно взятого потока -- использовать
TLS (thread local storage) и его аналоги.
Re: Как наиболее корректно работать с глобальными данными
L>Все вроде идеально, но тут будет ошибка линкования.
На таком уровне понимания проблемы тебе надо сначала почитать книжку по языку С++ или даже С,
потому что модели памяти у них одинаковые.
Почитай про классы памяти, области видимости и static.
В таком раскладе обе переменные auto g_log должны быть объявлены static, иначе будет нарушение ODR.
Да, и к работе потоков это не имеет никакого отношения (пока).
Здравствуйте, lnkuser, Вы писали:
L>но опять таки, много где пишут мол старайтесь избегать extern. А что использовать взамен тогда???
Одно слово: идиоты!
Не читай говнокнижги, не читай говноинтернет, не читай советы на форумах (на этом -- можно !)
Читай книги, правильные книги, и думай, что зачем, а не живи мифами, котрые кочуют из форума в форум.
Никак, просто правильно используют глобальные данные, и всё.
Ещё раз, если это многопоточность, то глобальные данные должны защищаться от совместного доступа.
L>Какие книги есть по данной тематике (а именно физический дизайн приложения), где бы описывалось как правильно строить программу.
Нет, нет таких книг. И да, есть такие книги -- любой элементарный учебник по С, даже по С, а не по С++,
классы памяти и области видимости переменных.
Потому что ты их не знаешь, и не понимаешь, у тебя всё в кучу в голове и мешается.
Доступ к данным при многопоточности -- это другая совсем тема, по этому есть очень компактная книжка, "Программирование на Linux, Профессиональный подход" Митчел, Оулдем, Самьюэл. Там надо главы 3, 4, 5 только прочитать.
Но учти, что multi-threaded программирование до поры было платформнозависимым, поэтому в Win всё то же самое, но
немного по-другому. Но идеи те же.
Re[2]: Как наиболее корректно работать с глобальными данными
Здравствуйте, MasterZiv, Вы писали:
MZ>Здравствуйте, lnkuser, Вы писали:
L>>но опять таки, много где пишут мол старайтесь избегать extern. А что использовать взамен тогда???
MZ>Одно слово: идиоты! MZ>Не читай говнокнижги, не читай говноинтернет, не читай советы на форумах (на этом -- можно !) MZ>Читай книги, правильные книги, и думай, что зачем, а не живи мифами, котрые кочуют из форума в форум.
Ну в контексте C++ в использовании extern действительно нет необходимости.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[3]: Как наиболее корректно работать с глобальными данными
Здравствуйте, enji, Вы писали:
E>Здравствуйте, VTT, Вы писали:
VTT>>Ну в контексте C++ в использовании extern действительно нет необходимости.
E>Это как?
Ну а для чего оно может использоваться? Глобальные переменные сподручнее делать в виде статических полей классов (да и использовать только в пределах этого класса), а трюк с шаблонами позволяет одновременно объявлять и определять эти поля в заголовочном файле.
Для внешних функций может понадобиться писать extern "C", но это уже мы попадаем в контекст C.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[5]: Как наиболее корректно работать с глобальными данными
Здравствуйте, VTT, Вы писали:
VTT>Ну а для чего оно может использоваться?
Для объявления глобальных переменных
VTT>Глобальные переменные сподручнее делать в виде статических полей классов (да и использовать только в пределах этого класса), а трюк с шаблонами позволяет одновременно объявлять и определять эти поля в заголовочном файле.
Ну с тем же успехом можно сказать, что и в for нет необходимости. Есть std::for_each, есть while
Иногда удобно использовать статический член класса, иногда — нет.
Re[6]: Как наиболее корректно работать с глобальными данными
Для меня это последний аргумент.
C++ это вообще не особо удобный язык: предварительное объявление всего и вся, крякозябры, экнмнабквх, неконсистентность, костыли, скудность средств "из коробки" и т.д.
Однако, по сравнению с C, в нем имеется и ряд прогрессивных нововведений, позволяющих защититься от множества непреднамеренных ошибок, повысить выразительность (и эффективность) языка. И наличие директив контроля доступа (пусть и в таком недоделанном виде) я считаю одним из самых полезных.
С другой стороны, плюрализма C тоже никто не отменял, и пользоваться всеми этими дополнительными средствами вроде как никто не заставляет.
Но каждый раз, когда мне хочется срезать угол, забить на контроль доступа, const-корректность, или даже просто назвать переменную покороче i вместо полноценного item_index, у меня возникают смутные подозрения, что я скатываюсь в трясину говнокода и этот срезанный угол мне когда-нибудь аукнется. Разумеется, использование этих средств вовсе не гарантирует отсутствие проблем в коде, но вот их неиспользование наличие проблем гарантирует.
И да, я скажу что C-шный трехсекционный for(;;) не нужен, C-style cast не нужен, С-style enum не нужен, объявление переменных через запятую не нужно, assignment chaining не нужен, malloc не нужен, ..., вымирание панд не нужно. Только вот все эти безобразия продолжат существовать несмотря на мое негодование.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
VTT>Для меня это последний аргумент.
Верю. но на ровном месте городить класс со статической переменной — это просто "синтаксический оверхед"
VTT>C++ это вообще не особо удобный язык: предварительное объявление всего и вся,
VTT>крякозябры,
это ты еще скалу не видел
VTT>экнмнабквх,
а? VTT>неконсистентность,
где?
VTT>просто назвать переменную покороче i вместо полноценного item_index,
ну батенька....
VTT>И да, я скажу что C-шный трехсекционный for(;;) не нужен,
Фанат бейсика — for i = 1 to 10 step 5? Или питона — for i in xrange(1, 10, 5)?
VTT>С-style enum не нужен
Почему?
VTT>объявление переменных через запятую не нужно,
Почему?
VTT>assignment chaining не нужен,
Почему?
Здравствуйте, enji, Вы писали:
VTT>>Для меня это последний аргумент. E>Верю. но на ровном месте городить класс со статической переменной — это просто "синтаксический оверхед"
Нет, это способ избежать неконтролируемого доступа к глобальным переменным.
VTT>>экнмнабквх, E>а? VTT>>неконсистентность, E>где?
Да куда не плюнь... Что синтаксис, что стандартная библиотека. Чтение и слева направо, и справа налево, и даже по спирали. Потоки ввода-вывода вообще какие-то наркоманы писали, чего только стоит плеяда eback, gptr, egptr, pbase, pptr, epptr, всякие sungetc xsputn, и кидание исключений по флажкам.
VTT>>просто назвать переменную покороче i вместо полноценного item_index, E>ну батенька....
VTT>>И да, я скажу что C-шный трехсекционный for(;;) не нужен, E>Фанат бейсика — for i = 1 to 10 step 5? Или питона — for i in xrange(1, 10, 5)?
Скорее фанат Кобола:
PERFORM VARYING COUNTER FROM 1 BY 5 UNTIL COUNTER > 10
VTT>>С-style enum не нужен E>Почему?
Ну в C enum это скорее замена пачки define. То, что в свежем стандарте C++ появилась возможность делать enum class, да еще и задавая базовый тип — большое достижение. Костыли типа attribute(packed) больше не нужны, и случаи неявного приведения к целочисленным типам (особенно когда они в качестве индекса используются) теперь легче отлавливать.
VTT>>объявление переменных через запятую не нужно E>Почему?
Несвязные действия в одном statement, полный винегрет с объявлением указателей и инициализацией, ненужные сложности с отладкой.
VTT>>assignment chaining не нужен E>Почему?
Несвязные действия в одном statement, чтение справа налево, ненужные сложности с отладкой, не имеет смысла при использовании move семантики.
Еще мне не нравится наличие у оператора присваивания возвращаемого значения (и требование возвращать ссылку для соответствия copy assignable), из-за этого случается нехилое количество факапов вида if(i = 42).
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[2]: Как наиболее корректно работать с глобальными данными
Здравствуйте, Don Reba, Вы писали:
DR>Здравствуйте, lnkuser, Вы писали:
L>>Как я понял это как раз случай для синглетона:
DR>Синглетоны — это те же глобальные объекты и часто считаются антипаттерном.
Почти правильно, но нужно уточнять: используйте фабрики. Если сейчас нужен синглтон, обойтись можно и фабрикой, которая будет возвращать один и тот же объект, но завтра от синглтона будет тяжело избавиться, а от фабрики будет избавляться не нужно.
Всё сказанное выше — личное мнение, если не указано обратное.