Как наиболее корректно работать с глобальными данными
От: lnkuser  
Дата: 03.07.15 08:36
Оценка:
Везде, абсолютно везде пишут что глобальные переменные, объекты это плохо и очень плохо.
Но нигде нормального ответа как обходится без них я не нашел. На форуме искал.

Допустим есть класс Log, который инициализируется при старте программы гдето в самом начале main().
И есть другие части программы которые работают в разных потоках, там тоже используется Log.

Как я понял это как раз случай для синглетона:

// other_module.cpp

auto g_log = Log::instance();
*g_log << loglevel::info << "some text";




// other_module2.cpp

auto g_log = Log::instance();
*g_log << loglevel::info << "some text2";



Все вроде идеально, но тут будет ошибка линкования.


Другой рабочий вариант это через extern:

// main.hpp

std::shared_ptr<Log> g_log = nullptr;



// main.cpp

#include "main.hpp"

g_log = std::make_shared<Log>();


// other_module.cpp

extern std::shared_ptr<Log> log;



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


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


Спасибо
Re: Как наиболее корректно работать с глобальными данными
От: Шахтер Интернет  
Дата: 03.07.15 09:41
Оценка: +4 -1
Здравствуйте, lnkuser, Вы писали:

L>Везде, абсолютно везде пишут что глобальные переменные, объекты это плохо и очень плохо.


Это глупость. Например, хип есть глобальный объект и ты им (как правило неявно) пользуешься в большинстве программ.
То же самое относится к целому ряду объектов, например стандартные потоки ввода-вывода в консольных приложениях.

L>Но нигде нормального ответа как обходится без них я не нашел. На форуме искал.


А не нужно обходится без них. Не надо заниматься мазохизмом.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: Как наиболее корректно работать с глобальными данными
От: koenjihyakkei Россия  
Дата: 03.07.15 09:43
Оценка:
Так например:

*Log::instance() << loglevel::info << "some text";

Или можно в файле, где определен Log, для удобства сделать дефайн типа:

#define LOG *Log::instance()

тогда

LOG << loglevel::info << "some text";

И не понятно, зачем вообще в каждом файле создавать переменную для лога
Отредактировано 03.07.2015 9:46 koenjihyakkei . Предыдущая версия .
Re[2]: Как наиболее корректно работать с глобальными данными
От: Abyx Россия  
Дата: 03.07.15 10:48
Оценка: -1
Здравствуйте, Шахтер, Вы писали:

Ш>Это глупость. Например, хип есть глобальный объект и ты им (как правило неявно) пользуешься в большинстве программ.

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

Ш>То же самое относится к целому ряду объектов, например стандартные потоки ввода-вывода в консольных приложениях.

тожесамое. если где-то так написано, ты не можешь по-нормальному реюзнуть такой код в GUI программе.
In Zen We Trust
Re: Как наиболее корректно работать с глобальными данными
От: Abyx Россия  
Дата: 03.07.15 10:50
Оценка: -2
Здравствуйте, lnkuser, Вы писали:

Почитайте про Dependency Injection.
Один из примеров — это Boost.DI https://github.com/krzysztof-jusiak/di
In Zen We Trust
Re: Как наиболее корректно работать с глобальными данными
От: Хон Гиль Дон Россия  
Дата: 03.07.15 11:04
Оценка: +4 -1
Здравствуйте, lnkuser, Вы писали:

L>Везде, абсолютно везде пишут что глобальные переменные, объекты это плохо и очень плохо.


Это часто пишут люди, которые не вполне разобрались в трудах классиков, зато расширили и углубили мысль. А классики сначала ругали исключительно глобальные переменные. Да, использование глобальных переменных в стиле говнокода 80-х (довелось несколько программ портировать) — это очень плохо. Но это крайне редко встречающийся сейчас случай. Растет культурка программирования-то.

L>Но нигде нормального ответа как обходится без них я не нашел. На форуме искал.


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

L>Допустим есть класс Log, который инициализируется при старте программы гдето в самом начале main().

L>И есть другие части программы которые работают в разных потоках, там тоже используется Log.

L>Как я понял это как раз случай для синглетона:


L>
L>// other_module.cpp

L>auto g_log = Log::instance();
L>*g_log << loglevel::info << "some text";
L>




L>
L>// other_module2.cpp

L>auto g_log = Log::instance();
L>*g_log << loglevel::info << "some text2";
L>



Можно и синглетон, но обычно удобнее просто свободные функции.

L>Все вроде идеально, но тут будет ошибка линкования.


С фигов ли, если писать правильно?


L>Другой рабочий вариант это через extern:


L>но опять таки, много где пишут мол старайтесь избегать extern.


Да можно и экстерн, но это ж потроха наружу. Чуть что поменял — и вся программа перекомпилируется. Фтопку.

L> А что использовать взамен тогда???

L>Как правильно спроектированные приложения решают проблему глобальных данных?

Функции. Хип в пример уже приводили — делайте так же.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[3]: Как наиболее корректно работать с глобальными данными
От: Evgeny.Panasyuk Россия  
Дата: 03.07.15 12:38
Оценка:
Здравствуйте, Abyx, Вы писали:

Ш>>То же самое относится к целому ряду объектов, например стандартные потоки ввода-вывода в консольных приложениях.

A>тожесамое. если где-то так написано, ты не можешь по-нормальному реюзнуть такой код в GUI программе.

Стандартные потоки можно перенаправить куда угодно через rdbuf, хоть в тот же GUI.
Re: Как наиболее корректно работать с глобальными данными
От: Don Reba Канада https://stackoverflow.com/users/49329/don-reba
Дата: 03.07.15 12:51
Оценка: +2
Здравствуйте, lnkuser, Вы писали:

L>Как я понял это как раз случай для синглетона:


Синглетоны — это те же глобальные объекты и часто считаются антипаттерном.
Ce n'est que pour vous dire ce que je vous dis.
Re: Как наиболее корректно работать с глобальными данными
От: Evgeny.Panasyuk Россия  
Дата: 03.07.15 13:03
Оценка: +1
Здравствуйте, lnkuser, Вы писали:

L>Везде, абсолютно везде пишут что глобальные переменные, объекты это плохо и очень плохо.


А ещё пишут что goto и friend это плохо. Некоторые даже считают что обычные функции это плохо, и всё должно быть классом/объектом
Я практически для всего что фанатики называют considered harmful видел достойные применения.

L>Но нигде нормального ответа как обходится без них я не нашел. На форуме искал.


Если пытаться без них совсем по-честному, то нужно в каждую функцию использующую глобальное состояние передавать дополнительные параметры — плюсом здесь является чуть более простая кастомизация. Но в случае логирования это редко оправданно.
Как правило же используют те же самые глобальные объекты, но замаскированные под какими-нибудь buzzwords типа ServiceLocator или IoC-Container.

L>Все вроде идеально, но тут будет ошибка линкования.


Тема хорошо раскрыта в Modern C++ Design Александреску.
Один из простых вариантов — это так называемый Meyers Singleton:
// in header:
inline Log &log()
{
    static Log x;
    return x;
}
Отредактировано 03.07.2015 13:05 Evgeny.Panasyuk . Предыдущая версия .
Re[2]: Как наиболее корректно работать с глобальными данными
От: Ops Россия  
Дата: 03.07.15 13:32
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Один из простых вариантов — это так называемый Meyers Singleton:

EP>
EP>// in header:
EP>inline Log &log()
EP>{
EP>    static Log x;
EP>    return x;
EP>}
EP>


Однако ж грабли в VC до Nov 2013 CTP.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[3]: Как наиболее корректно работать с глобальными данными
От: Evgeny.Panasyuk Россия  
Дата: 03.07.15 13:36
Оценка:
Здравствуйте, Ops, Вы писали:

EP>>Один из простых вариантов — это так называемый Meyers Singleton:

EP>>
EP>>// in header:
EP>>inline Log &log()
EP>>{
EP>>    static Log x;
EP>>    return x;
EP>>}
EP>>

Ops>Однако ж грабли в VC до Nov 2013 CTP.

Ты о thread-safe initialization? Так она вполть до C++11 не гарантировалась, а как это обходится — есть в упомянутой книге.
Отредактировано 03.07.2015 13:36 Evgeny.Panasyuk . Предыдущая версия .
Re[3]: Как наиболее корректно работать с глобальными данными
От: Шахтер Интернет  
Дата: 03.07.15 13:48
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Здравствуйте, Шахтер, Вы писали:


Ш>>Это глупость. Например, хип есть глобальный объект и ты им (как правило неявно) пользуешься в большинстве программ.

A>во многих библиотеках делают возможность задать свои функции аллокации/деаллокации, и хип напрямую не используется.

И что, хип после этого перестаёт быть глобальным объектом?
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[3]: Как наиболее корректно работать с глобальными данными
От: Хон Гиль Дон Россия  
Дата: 03.07.15 14:00
Оценка:
Здравствуйте, Abyx, Вы писали:


Ш>>Это глупость. Например, хип есть глобальный объект и ты им (как правило неявно) пользуешься в большинстве программ.

A>во многих библиотеках делают возможность задать свои функции аллокации/деаллокации, и хип напрямую не используется.

Ага, одна пара маллок/фри на всю либу. То есть глобальные. Последовательного применения dependency injection, шоп например в каждый объект тащить аллокатор, логгер и конфиг, я в реальном вменяемом коде ни разу пока не видел. Не, я допускаю, что области где они сплошь требуются, бывают, но советовать это по умолчанию по-моему перебор.

Ш>>То же самое относится к целому ряду объектов, например стандартные потоки ввода-вывода в консольных приложениях.

A>тожесамое. если где-то так написано, ты не можешь по-нормальному реюзнуть такой код в GUI программе.

Чтобы с этим не было проблем, достаточно обеспечить подключаемые реализации глобальных объектов с разумными умолчаниями. Например, логгер никуда не выводит, аллокатор вызывает CRT'шные malloc/free, конфиг возвращает какие-то дефолты. А DI уместнее оставить для случаев, где неявных границ нет, а разное поведение требуется.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re: Как наиболее корректно работать с глобальными данными
От: vpchelko  
Дата: 03.07.15 19:24
Оценка:
Здравствуйте, lnkuser, Вы писали:

L>Везде, абсолютно везде пишут что глобальные переменные, объекты это плохо и очень плохо.

L>Но нигде нормального ответа как обходится без них я не нашел. На форуме искал.

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

L>Допустим есть класс Log, который инициализируется при старте программы гдето в самом начале main().

L>И есть другие части программы которые работают в разных потоках, там тоже используется Log.

Если твой логер будет использоваться в деструкторе глобальных объектов — будет больно.

L>Как я понял это как раз случай для синглетона:

L>но опять таки, много где пишут мол старайтесь избегать extern. А что использовать взамен тогда???
L>Как правильно спроектированные приложения решают проблему глобальных данных?

Избегать в больших проектах, ибо потом заманаешься фантомные баги фиксить.

А также большие проблемы с доступом к глобальным переменным. Какие гарантии thread safe? Сильно мешает отладке.

И любой чих — может положить все приложение.

Например в хроме, всякие там дескрипторы хранятся в глобальных мапах. И как последствие регулярные баги связные с этим приколом.

L>Какие книги есть по данной тематике (а именно физический дизайн приложения), где бы описывалось как правильно строить программу.


L>Спасибо
Сало Украине, Героям Сала
Отредактировано 03.07.2015 19:40 vpchelko . Предыдущая версия . Еще …
Отредактировано 03.07.2015 19:31 vpchelko . Предыдущая версия .
Отредактировано 03.07.2015 19:28 vpchelko . Предыдущая версия .
Отредактировано 03.07.2015 19:25 vpchelko . Предыдущая версия .
Re[4]: Как наиболее корректно работать с глобальными данными
От: Abyx Россия  
Дата: 03.07.15 20:16
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

Ш>>>То же самое относится к целому ряду объектов, например стандартные потоки ввода-вывода в консольных приложениях.

A>>тожесамое. если где-то так написано, ты не можешь по-нормальному реюзнуть такой код в GUI программе.

EP>Стандартные потоки можно перенаправить куда угодно через rdbuf, хоть в тот же GUI.


но только глобально для всей программы.
In Zen We Trust
Re[4]: Как наиболее корректно работать с глобальными данными
От: Abyx Россия  
Дата: 03.07.15 20:23
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>>>Это глупость. Например, хип есть глобальный объект и ты им (как правило неявно) пользуешься в большинстве программ.

A>>во многих библиотеках делают возможность задать свои функции аллокации/деаллокации, и хип напрямую не используется.

Ш>И что, хип после этого перестаёт быть глобальным объектом?


да, перестает.
в той же винде можно создать несколько разных хипов для разных потоков или модулей программы.
или например можно использовать arena allocator'ы.
In Zen We Trust
Re[4]: Как наиболее корректно работать с глобальными данными
От: Abyx Россия  
Дата: 03.07.15 20:57
Оценка:
Здравствуйте, Хон Гиль Дон, Вы писали:

ХГД>Здравствуйте, Abyx, Вы писали:



Ш>>>Это глупость. Например, хип есть глобальный объект и ты им (как правило неявно) пользуешься в большинстве программ.

A>>во многих библиотеках делают возможность задать свои функции аллокации/деаллокации, и хип напрямую не используется.

ХГД>Ага, одна пара маллок/фри на всю либу. То есть глобальные. Последовательного применения dependency injection, шоп например в каждый объект тащить аллокатор, логгер и конфиг, я в реальном вменяемом коде ни разу пока не видел. Не, я допускаю, что области где они сплошь требуются, бывают, но советовать это по умолчанию по-моему перебор.


ну вот есть 7zip, там так и сделано.
если бы оттуда COM выпилить — был бы совсем хороший код %)

а вообще если использовать что-то типа DI-контейнера/сервис-локатора, то код получается довольно простым.
using MyDIContainer = std::tuple<Logger*, Config*, ...>;

class Foo {
public:
  Foo(const MyDIContainer& di_container) {
    auto cfg = get<Config*>(di_container); // std::get
    ...
    log(di_container, "something", 123); // calls "if (auto logger = get<Logger*>(di_container)) logger->log(args...);"
    ...
    auto bar = allocate<Bar>(di_container, 456); // calls "get<Allocator*>(di_container)->Create<T>(args...);"
  };
};


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

Ш>>>То же самое относится к целому ряду объектов, например стандартные потоки ввода-вывода в консольных приложениях.

A>>тожесамое. если где-то так написано, ты не можешь по-нормальному реюзнуть такой код в GUI программе.

ХГД>Чтобы с этим не было проблем, достаточно обеспечить подключаемые реализации глобальных объектов с разумными умолчаниями. Например, логгер никуда не выводит, аллокатор вызывает CRT'шные malloc/free, конфиг возвращает какие-то дефолты. А DI уместнее оставить для случаев, где неявных границ нет, а разное поведение требуется.


Всё это хорошо до тех пор, пока тебе не понадобится например запустить две функции параллельно, и дать каждой свой логгер.
Или вызвать void foo(function<void()> delegate); так чтобы код в foo писал в логгер, а delegate туда не писал.
Можно конечно запилить демультиплексор, но все же лучше иметь возможность задавать свои логи.
In Zen We Trust
Re[5]: Как наиболее корректно работать с глобальными данными
От: Evgeny.Panasyuk Россия  
Дата: 03.07.15 23:09
Оценка:
Здравствуйте, Abyx, Вы писали:

Ш>>>>То же самое относится к целому ряду объектов, например стандартные потоки ввода-вывода в консольных приложениях.

A>>>тожесамое. если где-то так написано, ты не можешь по-нормальному реюзнуть такой код в GUI программе.
EP>>Стандартные потоки можно перенаправить куда угодно через rdbuf, хоть в тот же GUI.
A>но только глобально для всей программы.

При необходимости не трудно сделать scope/region-based — то есть guard'ы позволят использовать разные логгеры на разных отрезках callstack. Плюс можно сделать специализацию по потокам через TLS или thread_id.

Обычно же одного логгера достаточно на всё приложение. Если конечно требуется управлять несколькими потоками логирования, или вообще задача в том чтобы аггрегировать и перенаправлять логи — то тут естественно они нужны в явном виде.
Re[5]: Как наиболее корректно работать с глобальными данными
От: Evgeny.Panasyuk Россия  
Дата: 03.07.15 23:42
Оценка:
Здравствуйте, Abyx, Вы писали:

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>


У тебя здесь обычный внешний параметр, пусть и составной.

IoC/DI-контейнер же это некоторый объект, как правило глобальный, который умеет создавать объекты требующие зависимости. Сами зависимые классы его не видят, и о нём ничего не знают.
Конкретные зависимости выбираются в соответствии с настроенными правилами — например можно указать что объекту Bar всегда давать логгер типа LoggerBar, или например внутри каждого потока (или даже соединения) использовать отдельный логгер.

Вот первый попавшийся пример (с кучей опечаток ):
public interface IWeapon  
{  
    void sord();  
}  

public class Ninja : IWeapon  
{  
    public void sord()  
    {  
        Console.WriteLine("I am using Sord");  
    }  
}  

public class sourav  
{  
    IWeapon ObjWeapon = null;  
    public sourav(IWeapon tmpWeapon)  
    {  
        this.ObjWeapon = tmpWeapon;  
    }  

    public void Attack()  
    {  
        this.ObjWeapon.sord();  
    }  
}  

class Program  
{  
    static void Main(string[] args)  
    {  
        Ninject.IKernel kernal = new StandardKernel();  
        kernal.Bind<IWeapon>().To<Ninja>();  
        var instance = kernal.Get<sourav>();  
        instance.Attack();   
        Console.ReadLine();  
    }  
}
Отредактировано 03.07.2015 23:44 Evgeny.Panasyuk . Предыдущая версия .
Re[2]: Как наиболее корректно работать с глобальными данными
От: lnkuser  
Дата: 04.07.15 06:52
Оценка:
A>Почитайте про Dependency Injection.
A>Один из примеров — это Boost.DI https://github.com/krzysztof-jusiak/di

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