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


Автор: Wound 12.07.18, 08:46
Всем привет.

В общем есть лог файл, хочу выводить в него инфу как в потоки.
Все нормально и отлично, но из за специфики дизайна, что то встал на написании манипулятора.
Я умом понимаю что он хочет, и что нужно сделать, но не пойму как.
По задумке я хочу писать вот так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    Logger log;
    log << "param1" << 1 << "ololo" << io::endl;

И оно все пишет и работает, но не вызывается манипулятор io::endl;
Он нужен для того, чтобы сбросить содержимое внутреннего потока в файл.
Если например переписать вот так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    Logger log;
    log << "param1" << 1 << "ololo" << L'\n';
    log.flush();

То все работает просто великолепно, как и задумывалось. Но это же нужно писать вырвиглазный L'\n' + log.flush делать.
Конкретно эти две строчки я хотел запихнуть в io::endl манипулятор, и мне это почти удалось.
Но есть одна загвоздка, не могу понять как перегрузить << для WrappedStream, чтоб он вызвал нужную мне версию.
У меня при любом раскладе вызывается в logger << вот это -> из класса Logger
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
                    template<class T>
            WrappedStream<std::wostringstream> operator<<(const T& arg)
            {
                return WrappedStream<std::wostringstream>(m_stream, m_streammtx) << arg;
            }

А при последующих вызовах, вызывается << у WrappedStream
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<typename T>
            WrappedStream& operator<<(const T& arg)
            {
                if(m_gstream != nullptr)
                    m_gstream->write(arg);
                return *this;
            }

Надо ему как то сказать что хорошо было бы вызвать friend WrappedStream<T> operator<< (Logger& stream, const std::function<WrappedStream<T>(Logger&)>& pFn);
Когда в него передают io::endl
Но я не пойму как, я уже все перепробовал. В итоге в лучше случае выводится адресс функции io::endl, вместо того, чтобы она выполнялась.
Где я ступил?
Спасибо, код прилагаю

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        template<typename TStream>
        class GuardStream
        {
        public:
            GuardStream(const GuardStream&) = delete;
            GuardStream(GuardStream&&) = delete;
            GuardStream& operator=(const GuardStream&) = delete;
            GuardStream& operator=(GuardStream&&) = delete;
     
            GuardStream(TStream& stream, std::mutex& mtx)
            : m_stream(stream),
              m_mutex(mtx)
            {
            }
     
            ~GuardStream()
            {
                m_stream.flush();
            }
     
            template<typename Arg>
            void write(const Arg& arg)
            {
                m_stream << arg;
            }
     
        private:
            std::lock_guard<std::mutex> m_mutex;
            TStream& m_stream;
        };
     
        template<typename TStream>
        class WrappedStream
        {
        public:
            WrappedStream(WrappedStream&& rhs)
            {
                if (rhs.m_gstream != nullptr)
                    m_gstream.swap(rhs.m_gstream);
            }
     
            WrappedStream(const WrappedStream& rhs)
            {
                if (rhs.m_gstream != nullptr)
                    m_gstream.swap(rhs.m_gstream);
            }
     
            WrappedStream& operator=(WrappedStream&&)
            {
                if (this != &rhs && rhs.m_gstream != nullptr)
                    m_gstream.swap(rhs.m_gstream);
            }
     
            WrappedStream& operator=(const WrappedStream&)
            {
                if (this != &rhs && rhs.m_gstream != nullptr)
                    m_gstream.swap(rhs.m_gstream);
            }
     
            WrappedStream(TStream& stream, std::mutex& mutex)
            : m_gstream( std::make_unique<GuardStream<TStream>>(stream, mutex) )
            {
            }
     
            template<typename T>
            WrappedStream& operator<<(const T& arg)
            {
                if(m_gstream != nullptr)
                    m_gstream->write(arg);
                return *this;
            }
     
            template<typename T>
                    friend WrappedStream<T> operator<< (Logger& stream, const std::function<WrappedStream<T>(Logger&)>& pFn);
     
        private:
            mutable std::unique_ptr<GuardStream<TStream>> m_gstream;
        };
     
        template<typename T>
        WrappedStream<T> operator<< (Logger& stream, const std::function<WrappedStream<T>(Logger&)>& pFn)
        {
            return pFn(stream);
        }
     
        class Logger
        {
        public:
            Logger(const std::wstring& logname, unsigned int cachesize=100);
            void flush();
     
            template<typename T>
            WrappedStream<std::wostringstream> operator<<(const std::function<WrappedStream<T>(Logger&)>& arg)
            {
                return WrappedStream<std::wostringstream>(m_stream, m_streammtx) << arg;
            }
     
            template<class T>
            WrappedStream<std::wostringstream> operator<<(const T& arg)
            {
                return WrappedStream<std::wostringstream>(m_stream, m_streammtx) << arg;
            }
     
            //template<>
            //WrappedStream<std::wostringstream> operator<<(const Logger& arg)
            //{
            //  return WrappedStream<std::wostringstream>(m_stream, m_streammtx) << arg;
            //}
        };
     
        template<class T>
        inline auto operator<< (WrappedStream<std::wostringstream>& stream, const T& pFn) -> decltype(pFn(stream))
        {
            return pFn(stream);
        }
     
        inline WrappedStream<std::wostringstream> operator<<(Logger& logger, std::function<WrappedStream<std::wostringstream>(Logger&)>& s)
        {
            return s(logger);
        }
     
        namespace io
        {
            inline WrappedStream<std::wostringstream> endl(Logger& rhs)
            {
                WrappedStream<std::wostringstream> stream(rhs.m_stream, rhs.m_streammtx); //! Так делать нельзя!!! нужно найти другой способ!!!
                stream << L'\n';
                            rhs.flush();
                return stream;
            }

Автор: Qraizer 12.07.18, 09:19
Ты уверен, что код полный?

Автор: Wound 12.07.18, 09:23
Цитата Qraizer @
Ты уверен, что код полный?

Не, он обрезанный.
Тут реализация всех функций/методов - которые участвуют в формировании лога.
Щас попробую Сделать работоспособным этот кусок.

Добавлено
Ну вот допилил до тестового примера:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    namespace test
    {
     
        struct GuardStream
        {
            GuardStream() = delete;
            GuardStream(const GuardStream&) = delete;
            void operator=(const GuardStream&) = delete;
     
            GuardStream(std::ostream& ostream, std::mutex& mutex)
            : m_stream(ostream),
              m_gmtx(mutex)
            {
            }
     
            ~GuardStream()
            {
                m_stream.flush();
            }
     
            template<typename T>
            void write(const T& x)
            {
                m_stream << x;
            }
            std::ostream& m_stream;
            std::lock_guard<std::mutex> m_gmtx;
        };
     
        struct WrappedStream
        {
            WrappedStream() = delete;
            void operator=(const WrappedStream&) = delete;
            WrappedStream(std::ostream& ostream, std::mutex& mutex)
            : m_gstream( std::make_unique<GuardStream>(ostream, mutex))
            {
            }
     
            WrappedStream(const WrappedStream& rhs)
            {
                m_gstream.swap(rhs.m_gstream);
            }
     
            template<typename T>
            WrappedStream& operator<<(const T& x)
            {
                m_gstream->write(x);
                return *this;
            }
            mutable std::unique_ptr<GuardStream> m_gstream;
        };
     
        struct Logger
        {
     
            explicit Logger(std::ostream& ostream) : ostream_(ostream)
            {
            }
     
            WrappedStream flush()
            {
                return WrappedStream(ostream_, mutex_) << "flush";
            }
     
            template<typename T>
            WrappedStream operator<<(const T& x)
            {
                return WrappedStream(ostream_, mutex_) << x;
            }
     
            std::ostream& ostream_;
            std::mutex mutex_;
        };
     
        template<class T>
        inline auto operator<< (WrappedStream& stream, const T& pFn) -> decltype(pFn(stream))
        {
            return pFn(stream);
        }
     
        //template<class T>
        //auto operator<< (WrappedStream<std::wostringstream>& stream, const T& pFn) -> decltype(pFn(stream))
        //{
        //  return pFn(stream);
        //}
     
        inline WrappedStream operator<<(Logger& logger, std::function<WrappedStream(Logger&)>& s)
        {
            return s(logger);
        }
     
        namespace io
        {
            inline WrappedStream endl(Logger& rhs)
            {
                return rhs.flush();
            }
        }
     
        Logger wrap_cout(std::cout);
    }

Юзать так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    test::wrap_cout << "Thread [" << std::this_thread::get_id() << "] put message: elapsed time: " << 1 << test::io::endl;


Добавлено
Вот в ideone выложил https://ideone.com/1JFO4Z

Автор: Олег М 12.07.18, 11:11
Цитата Wound @
    inline WrappedStream operator<<(Logger& logger, std::function<WrappedStream(Logger&)>& s)
    {
        return s(logger);
    }
 

Там надо operator<<(WrappedStream<std::wostringstream>(*fn)(Logger&)), или что-то типа того

Автор: Wound 12.07.18, 11:15
Цитата Олег М @
Там надо operator<<(WrappedStream<std::wostringstream>(*fn)(Logger&)), или что-то типа того

Да, я пробовал уже. Хоть убей не заходит в глоьальный.
Но, если объявить вот такой в классе WrappedStream:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
            template<typename T>
            WrappedStream operator<<(test::WrappedStream(*)(T& arg))
            {
                return *this;
            }

То в него заходит. Правда не понятно как теперь вызвать эту функцию, она же параметр должна принимать.

Добавлено
Разве что передавать ссылку на родительский класс внутрь WrappedStream

Автор: Qraizer 12.07.18, 11:24
Во. Так лучше.

Автор: Wound 12.07.18, 11:40
Цитата Qraizer @
Во. Так лучше.

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

Автор: Qraizer 12.07.18, 11:49
Не, я про дополненный код.
Короче. В итоге там зовётся test::GuardStream::write<test::WrappedStream (*)(test::Logger&)>(), который для std::basic_ostream<> зовёт operator <<() с переданным параметром, но std ничего не знает про перегрузку operator<< для test::WrappedStream (*)(test::Logger&), она знает лишь перегрузку для std::basic_ios<>& (*)(std::basic_ios<>&). Поэтому и срабатывает каст указателя на функцию в void*.

Автор: Wound 12.07.18, 11:55
Цитата Qraizer @
но std ничего не знает про перегрузку operator<< для test::WrappedStream (*)(test::Logger&), она знает лишь перегрузку для std::basic_ios<>& (*)(std::basic_ios<>&)

Да, я классу WrappedStream уже сделал делал такой оператор, но в таком случае WrappedStream ничего не знает о Logger :(

Добавлено
Передал ему ссылку на Logger, в итоге выхватил исключение при попытке залочить мьютекс. Может быть тут можно как то дизайн пересмотреть, а то у меня голова уже не варит, с 3 ночи сижу пытаюсь этот манипулятор написать. Мне кажется я уже все варианты перепробовал.

Добавлено
Тут у меня просто была проблема в том, что когда пишешь в лог наподобии:
log << param1 << param2 << paramN
из нескольких потоков, то сообщения от разных потоков формируются в одно и получается мешанина.
Я начал с этим извращаться, вот доизвращался до такого варианта и все работает вроде как, но вот на манипуляторе сел в лужу :-?

Автор: Qraizer 12.07.18, 12:13
Ну да, перемудрил, пожалуй. Тебе было надо лишь залочить логгер до конца выражения. Для этого достаточно временного Guard, лочащего агрегированный в логгер std::basic_ostream, и отпускающего его в деструкторе, а сами << пусть бы работали сами по себе, стандартные. И не надо было бы никаких враперов.

Добавлено
Было бы что-то типа
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    Guard(log) << param1 << param2 << paramN;
Одно но: без Guard вся система рушится, так что все операции с log должны начинаться с этого самого Guard(log).

Автор: Wound 12.07.18, 12:24
Цитата Qraizer @
а сами << пусть бы работали сами по себе, стандартные. И не надо было бы никаких враперов.

Да дело в том, что они у меня не стандартные, это тут в примере используется std::cout, и тут можно обычный std::endl юзнуть, а у меня вместо потока на самом деле стоит std::wostringstream, в него std::endl не выводится. Плюс у меня есть функция flush, которая скидывает данные во внутреннюю очередь. А потом отдельный поток в логе, когда очередь забивается до определенного размера, скидывает ее в файл.
Поэтому и пришлось извращаться с нестандартным манипулятором.

Добавлено
Вернее с std::endl я погарячился немного, я хотел flush вызывать внутри манипулятора, чтобы скидывать во внутренюю очередь сформированную строку.

Автор: Qraizer 12.07.18, 12:43
Та ну и какая разница? Они все наследуются от std::basic_ios<>, а манипуляторы на него завязаны..

Добавлено
Вот, нашёл ссылку. С трудом
Маразм программёров (сообщение #3017166)

Автор: Wound 12.07.18, 12:57
Цитата Qraizer @
Вот, нашёл ссылку. С трудом
Маразм программёров (сообщение #3017166)

Ну так это больше похоже на тестовый вариант что тут выложил выше, он то тоже работает с потоками ostream. Т.е. тут в твоем примере работа идет напрямую с потоком. А в моем случае работа идет напрямую с очередью std::wstring'ов, т.е. пишется оно в буфер, а уж потом из буфера записывается в поток.


Цитата Qraizer @
Та ну и какая разница? Они все наследуются от std::basic_ios<>, а манипуляторы на него завязаны..

Так я не знаю где мне вызвать функцию flush :-? ее нужно вызвать после формирования строки в поток. Вот я и думал замаскировать ее под манипулятор.
Вот это вот flush:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        void Logger::write(const std::wstring& string)
        {
            std::unique_lock<std::mutex> mIsFlushed(m_mutex_flusher);
            if (m_main_log.size() >= m_cachesize)
            {
                m_bIsQueueReady.store(false);
                m_flusher.notify_one();        
                m_cleaner.wait(mIsFlushed, [this] { return m_bIsQueueReady.load(); });
            }
            m_main_log.push(string);
        }

Автор: ЫукпШ 12.07.18, 13:12
Цитата Wound @
Так я не знаю где мне вызвать функцию flush :-? ее нужно вызвать после формирования строки в поток.

Ты знаешь какой тип у "io::endl" ?
Сделай с аргументом-ссылкой на этот тип процедуру "operator<<".
И сделай в этой процедуре вывод CRLF в файл вместе с flush.
---
У тебя всё как-то сложно получилось.
Можно сделать процедуру записи в лог с переменным числом параметров типа как "printf".

Автор: Wound 12.07.18, 13:15
Цитата ЫукпШ @
Ты знаешь какой тип у "io::endl" ?

Это мой манипулятор, я его сам написал. Могу любой тип ему сделать :)

Цитата ЫукпШ @
Сделай с аргументом-ссылкой на этот тип процедуру "operator<<".
И сделай в этой процедуре вывод CRLF в файл вместе с flush.

Вот, в этом и проблема. Вот код выше в ideone есть, он работает, компилируется и запускается, но вместо вызова io::endl, пишется ее адрес в поток. Адрес пишется, потому что класс, который выводит в поток, ничего не знает о логере, в котором реализован метод flush. Вот в примере выше - я пытался вызвать flush, пока что безуспешно :-?

Добавлено
Цитата ЫукпШ @
Можно сделать процедуру записи в лог с переменным числом параметров типа как "printf".

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

Добавлено
И вообще можно ли такое реализовать? Но ведь стандартный std::endl ведь как то работает, значит ведь можно ведь как то это сделать :scratch:
Унаследоваться и напрямую все перегрузить?

Автор: ЫукпШ 12.07.18, 13:23
Цитата Wound @
Цитата ЫукпШ @
Ты знаешь какой тип у "io::endl" ?

Это мой манипулятор, я его сам написал. Могу любой тип ему сделать :)

Но это у тебя работает ?
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    log << "param1" << 1 << "ololo" << L'\n';
    log.flush();
Вот и вызови это
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ..<< L'\n';
    log.flush();

из процедуры "operator<< (ios::endl)"
-----
Ты ничего не перепутал ?
\n - это LF.
Если текстовый файл для LINUX этого достаточно, а для Виндус - нет.

Автор: Wound 12.07.18, 13:35
Цитата ЫукпШ @
Вот и вызови это

Как?
Вот смотри: https://ideone.com/1JFO4Z
Как его вызвать? У меня не получается, можешь плз написать? Или скажи что куда нужно написать?

Добавлено
ЫукпШ, тут проблема в том, что вывод идет в классе WrappedStream, а метод flush у Logger; WrapperStream ничего не знает о Logger, так как он находится на уровне ниже Logger'а. Его использует Logger.

Автор: ЫукпШ 12.07.18, 14:38
Цитата Wound @
Как?
Вот смотри: https://ideone.com/1JFO4Z

Да я не хочу во всём это разбираться. Тем более, что я считаю всё это сложным и лишним.

Добавлено
Цитата Wound @
тут проблема в том, что вывод идет в классе WrappedStream, а метод flush у Logger; WrapperStream ничего не знает о Logger, так как он находится на уровне ниже Logger'а.

Но судя по твоему вопросу ты архитектурно зашёл в тупик.

Автор: Wound 12.07.18, 15:29
Цитата ЫукпШ @
Но судя по твоему вопросу ты архитектурно зашёл в тупик.

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

Автор: JoeUser 12.07.18, 18:04
Цитата ЫукпШ @
Ты ничего не перепутал ?
\n - это LF.
Если текстовый файл для LINUX этого достаточно, а для Виндус - нет.

Достаточно всегда!

Пруф:
Цитата
A text stream is an ordered sequence of characters composed into lines (zero or more characters plus a terminating '\n'). Whether the last line requires a terminating '\n' is implementation-defined. Characters may have to be added, altered, or deleted on input and output to conform to the conventions for representing text in the OS (in particular, C streams on Windows OS convert \n to \r\n on output, and convert \r\n to \n on input)

Автор: Qraizer 12.07.18, 18:29
В общем и целом резюмирую.
Я не вижу препятствий, почему бы всё не решить методом, подобным как описано в маразмах. Но это другая архитектура, переписывать надо. В той реализации, как у тебя, endl вообще не вызывается в log << "param1" << 1 << "ololo" << endl;, там только берётся указатель на endl. Использование этого указателя в лучшем случае происходит лишь во WrappedStream, где всё равно информация о Logger уже потеряна.

Добавлено
Что-то типа
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        class Logger;
     
        struct WrappedStream
        {
    /* ... */
            WrappedStream(std::ostream& ostream, std::recursive_mutex& mutex, Logger& log)
            : m_gstream( std::make_unique<GuardStream>(ostream, mutex)), logger(log)
            {
            }
     
            WrappedStream(const WrappedStream& rhs): logger(rhs.logger)
            {
                m_gstream.swap(rhs.m_gstream);
            }
    /* ... */
            WrappedStream operator<< (WrappedStream pFn(Logger&))
            {
                return pFn(logger);
            }
     
            mutable std::unique_ptr<GuardStream> m_gstream;
            Logger& logger;
        };
     
        struct Logger
        {
    /* ... */
            WrappedStream flush()
            {
                return WrappedStream(ostream_, mutex_, *this) << "flush";
            }
     
            template<typename T>
            WrappedStream operator<<(const T& x)
            {
                return WrappedStream(ostream_, mutex_, *this) << x;
            }
     
            std::ostream& ostream_;
            std::recursive_mutex mutex_;
        };
И – да, мьютекс нужен рекурсивный.

Автор: Wound 12.07.18, 18:41
Цитата Qraizer @
В той реализации, как у тебя, endl вообще не вызывается в log << "param1" << 1 << "ololo" << endl;, там только берётся указатель на endl. Использование этого указателя в лучшем случае происходит лишь во WrappedStream, где всё равно информация о Logger уже потеряна.

Ок, есть ли способы переписать мой вариант по другому, но чтобы семантика вызова не изменилась? Если есть - можно хоть пнуть в нужном направлении?
Если нет, то в таком случае, я был лучшего мнения о новом С++. :-?

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

Автор: JoeUser 12.07.18, 20:17
Wound, а если вот в такой код немножко накидать лок-гвардов?
... или я не про то? :-?

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <iostream>
    #include <fstream>
     
    using namespace std;
     
    class LoggedStream {
      private:
        ostream& out;
      public:
        LoggedStream(ostream& o):out(o){}
        template<typename T>
        const LoggedStream& operator<<(const T& v) const {
          out << v;
          return *this;
        }
        LoggedStream const& operator<<(std::ostream& (*func)(std::ostream&)) const {
          func(out);
          return *this;
        }
    };
     
    int main(int,char**) {
      LoggedStream Log1(std::cout);
      Log1 << 1 << " 2" << endl << 3 << " 4" << endl;
      //
      std::ofstream ofs("test.txt", std::ofstream::out);
      LoggedStream Log2(ofs);
      Log2 << 1 << " 2" << endl << 3 << " 4" << endl;
    }

Автор: Qraizer 12.07.18, 20:39
Цитата Wound @
Ок, есть ли способы переписать мой вариант по другому, но чтобы семантика вызова не изменилась?
Э-э-э... уже. Не?

Автор: Wound 13.07.18, 10:20
Все таки я ради принципа добил!
Что вы думаете о таком извращении?
Не возникнет ли тут проблем никаких?
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #pragma once
     
    #include <sstream>
    #include <string>
    #include <memory>
    #include <mutex>
    #include <iostream>
    #include <fstream>
    #include <atomic>
    #include <condition_variable>
    #include <concurrent_queue.h>
     
    namespace core
    {
        class Logger
        {
            using lock_t = std::unique_lock<std::mutex>;
        public:
            Logger()
            : m_isFreeStream(true)
            {
            }
     
            Logger(Logger&&) = delete;
            Logger(const Logger&) = delete;
            Logger& operator=(Logger&&) = delete;
            Logger& operator=(const Logger&) = delete;
     
            ~Logger() = default;
     
            template<typename T>
            Logger& operator<< (T&& val)
            {
                lock_t guard_mtx(m_gmtx);
                m_condvar.wait(guard_mtx, [this] { return m_isFreeStream.load() || m_owner_thread_id == std::this_thread::get_id(); });
     
                m_isFreeStream.store(false);
                m_owner_thread_id = std::this_thread::get_id();
                
                m_stream << val;
                return *this;
            }
     
            friend Logger& operator<<(Logger& os, Logger& (*Pfn)(Logger&));
     
            void flush()
            {
                lock_t guard_mtx(m_gmtx);
                {
                    m_stream.flush();
                    m_queue.push(m_stream.str());
                    std::ostringstream tmp;
                    m_stream.swap(tmp);
                    m_isFreeStream.store(true);
                    std::thread::id tmpid;
                    m_owner_thread_id = tmpid;
                }
                m_condvar.notify_one();
            }
     
            void Save(const std::string& file)
            {
                std::ofstream fout;
                fout.open(file, std::ios_base::out | std::ios_base::trunc);
                if (!fout.is_open())
                    throw "Error: file not open";
     
                while (m_queue.unsafe_size() > 0u)
                {
                    std::string line;
                    if (m_queue.try_pop(line))
                        fout << line;
                }
                fout.close();
            }
     
        private:
            std::mutex m_gmtx;
            std::atomic_bool m_isFreeStream;
            std::ostringstream m_stream;
            concurrency::concurrent_queue<std::string> m_queue;
            std::condition_variable m_condvar;
            std::thread::id m_owner_thread_id;
        };
     
        Logger& operator<<(Logger& os, Logger& (*Pfn)(Logger&))
        {
            return Pfn(os);
        }
     
        namespace io
        {
            inline Logger& endl(Logger& stream)
            {
                stream << '\n';
                stream.flush();
                return stream;
            }
        }
    }

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include "Logger.h"
    #include <iostream>
    #include <thread>
    #include <vector>
    #include <chrono>
    #include <algorithm>
     
    void Test(core::Logger& logger, const std::string& message)
    {
        auto old = std::chrono::steady_clock::now();
        while (true)
        {
            auto now = std::chrono::steady_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - old).count();
            if (elapsed > 5)
                break;
     
            if (elapsed > 1)
                elapsed = elapsed;
     
            logger << "Thread [" << std::this_thread::get_id() << "] put message:" << message << "elapsed time: " << elapsed << core::io::endl;
        }
    }
     
    int main()
    {
        core::Logger log;
     
        log << "Ololo" << core::io::endl;
     
        std::vector<std::thread> threads;
     
        std::string msg1 = " Message one ";
        std::string msg2 = " Message two ";
        std::string msg3 = " Message three ";
        std::string msg4 = " Message four ";
     
        threads.emplace_back(Test, std::ref(log), std::ref(msg1));
        threads.emplace_back(Test, std::ref(log), std::ref(msg2));
        threads.emplace_back(Test, std::ref(log), std::ref(msg3));
        threads.emplace_back(Test, std::ref(log), std::ref(msg4));
     
        std::for_each(threads.begin(), threads.end(), [](auto&& item) { item.join(); });
     
     
        log.Save("test.log");
        std::cout << "Done...";
        std::cin.get();
        return 0;
    }

Автор: Qraizer 13.07.18, 10:33
Не могу заценить в полной мере, я на летнем корпоративе. Мой вариант немного другой и ИМХО попроще.

Автор: Wound 13.07.18, 10:38
Цитата Qraizer @
Не могу заценить в полной мере, я на летнем корпоративе. Мой вариант немного другой и ИМХО попроще.

Ну в твоем варианте у меня от количества шаблонов в глазах рябит :D
Тут все просто, основная идея такая - кто первый зашел в operator <<, тот и захватывает мьютекс, так же сохраняется id потока, который захватил IO поток.
В io::endl(внутри функции flush) поток захвативший IO поток, выгружает его в очередь, и оповещает остальные потоки о том, что он закончил с помощью условной переменной.

Автор: JoeUser 13.07.18, 10:41
Цитата Wound @
Что вы думаете о таком извращении?

Чисто имхо - не нравится архитектура.
Цитата Wound @
Не возникнет ли тут проблем никаких?

Очевидная - одна, строка 40:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    m_stream << val;

Запихивание данных в память (в std::ostringstream) бесконтрольное.
В принципе и запись в файловый поток должна бы проверяться, а тут в просто в память.
Жесткач короче :)

Автор: Wound 13.07.18, 11:15
Цитата JoeUser @
Очевидная - одна, строка 40:

Так нельзя делать. Это ограничение.

Цитата JoeUser @
Запихивание данных в память (в std::ostringstream) бесконтрольное.

В каком смысле бесконтрольное?

Цитата JoeUser @
В принципе и запись в файловый поток должна бы проверяться, а тут в просто в память.

Не совсем понял. Что должно проверятся куда?

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

Добавлено
Цитата JoeUser @
Жесткач короче :)

А в чем жесткач?

По поводу ограничения, ну можно в принципе вместо wait, написать wait_for, и flush вызвать в деструкторе(это уже где то в боевом логгере.)

Добавлено
Цитата JoeUser @
а тут в просто в память.

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

Автор: Олег М 13.07.18, 11:38
Цитата Wound @
А в чем жесткач?

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

Лучше эту задачу решать так - есть однопоточный класс Message, с оператором <<, который форматирует сообщение в какой-нибудь stringstream, и есть Logger, который под блокировкой сохраняет Message в файл:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    lass CLogger;
    class CMessage
    {
    public:
        CMessage(CLogger &logger)
        : m_logger(logger)
        {
        }
     
        ~CMessage()
        {
            Flush();
        }
     
        template <typename T>
        CMessage &operator <<(T &&val)
        {
            m_out << std::forward<T>(val);
            return *this;
        }
     
        void Flush() noexcept
        {
            m_logger.Flush(std::move(m_out));
        }
     
    protected:
        CLogger &m_logger;
        std::stringstream m_out;
    };
     
    class CLogger
    {
    public:
        CMessage Message()
        {
            return CMessage(*this);
        }
     
        void Flush(std::stringstream out) noexcept
        {
            std::lock_guard lock(m_mx);
            m_file << ...out....  << std::endl
        }
    protected:
     
        std::mutex m_mx;
        std::ofstream m_file;
    };
     
    ......................................
    CLogger log;
     
    .........
    log.Message() << 1 << 2 << 3;

Автор: Wound 13.07.18, 11:54
Цитата Олег М @
Лучше эту задачу решать так - есть однопоточный класс Message, с оператором <<, который форматирует сообщение в какой-нибудь stringstream, и есть Logger, который под блокировкой сохраняет Message в файл:

Хм, интересный подход. Надо будет попробовать.

Автор: JoeUser 13.07.18, 15:14
Цитата Wound @
Так нельзя делать. Это ограничение.

В смысле? :blink: Я тебе указал на 40 строку твоего кода и указал что там запись в память ни чем не проверяется. А вдруг лимит памяти уже настал? ... Но ты мне отвечаешь "Так нельзя делать. Это ограничение". Что нельзя делать? И где/что за ограничение?
Цитата Wound @
В каком смысле бесконтрольное?

Цитата Wound @
Не совсем понял. Что должно проверятся куда?

нет чека этого:
Errors are signaled by modifying the internal state flags, except for (3), that never sets any flags (but the particular manipulator applied may) ...
или этого:
If the operation sets an internal state flag that was registered with member exceptions, the function throws an exception of member type failure

Цитата Wound @
Это не окончательная версия, это тестовый пример

Ну тогда - сорь. Но проверку на ошибки не забудь!

Автор: Wound 13.07.18, 15:25
Цитата JoeUser @
В смысле? Я тебе указал на 40 строку твоего кода и указал что там запись в память ни чем не проверяется. А вдруг лимит памяти уже настал? ... Но ты мне отвечаешь "Так нельзя делать. Это ограничение". Что нельзя делать? И где/что за ограничение?

Ограничение вообще писал я про то, что нельзя без endl вызывать. А про память - это в данном случае не имеет значение, это тестовый класс, и проверять в нем лимит памяти смысла просто нет.

Автор: JoeUser 13.07.18, 16:39
Цитата Wound @
Ограничение вообще писал я про то, что нельзя без endl вызывать. А про память - это в данном случае не имеет значение, это тестовый класс, и проверять в нем лимит памяти смысла просто нет.

Оке, проехали :)

И все ж - глянь мой пример. Я думаю, можно не плодить кучу классов. Если в моем 'LoggerStream' добавить средства синхронизации - то он может "помогать" работать асинхронно любому потомку от std::ostream. Но я бы не трогал стримы в плане организации их эффективного кэширования - а завел бы отдельный класс, который был бы манагером кэширования. Считаю такой подход - рассово верным! :)

Скрытый текст
ЗЫ: Может быть поможет тебе где-то как-то в борьбе с стримами :lol:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <type_traits>
    #include <utility>
    #include <iostream>
    #include <sstream>
     
    template<typename S, typename T>
    class is_streamable {
      private:
        template<typename SS, typename TT>
        static auto test(int) -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type());
        template<typename, typename>
        static auto test(...) -> std::false_type;
      public:
        static const bool value = decltype(test<S,T>(0))::value;
    };
     
    class C {};
     
    int main() {
      std::cout << is_streamable<std::stringstream, C>::value << std::endl;
      std::cout << is_streamable<std::stringstream, int>::value << std::endl;
      return 0;
    }

Автор: Wound 14.07.18, 07:10
Цитата JoeUser @
Если в моем 'LoggerStream' добавить средства синхронизации - то он может "помогать" работать асинхронно любому потомку от std::ostream. Но я бы не трогал стримы в плане организации их эффективного кэширования - а завел бы отдельный класс, который был бы манагером кэширования.

Так у вас все пишется в файл сразу, а у меня прога и так очень активно с файлами работает, в прошлой теме же наговорили, что тупить будет, если одновременно в нескольких потоках работать с разными файлами, вот я и делаю логгер, чтоб он минимально работал с файлами, по умолчанию кеш у меня - это потокобезопасная очередь std::wstring'ов. В нее и пишу, а поток используется исключительно и только для формирования 1 строчки лога, потому как это удобнее, чем писать всякие извращения типа itoa, strcat и им подобные.
В данной теме, мне пока больше всего понравился подход Олег М. Там он простой и безопасный + нет кучи мьютексов и остального треша. Пока решил остановится на его задумке, темболее это здорово сокращает количество кода.

Автор: JoeUser 14.07.18, 10:07
Цитата Wound @
Так у вас все пишется в файл сразу

Так можно с буфером I/O поиграться:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <fstream>
    int main () {
      const size_t bufsize = 1024*1024;
      char *buff = new char[bufsize];
      std::ofstream ofs("test.txt", std::ofstream::out);
      ofs.rdbuf()->pubsetbuf(buff, bufsize);
      ofs << "Hello, buffered stream!\n";
      return 0;
    }


Добавлено
ЗЫ: Хотя да, надо будет как-то экспериментальным способом определять - когда пользовать "\n", а когда std::endl.
От этого зависит момент сброс буфера.

Автор: Wound 14.07.18, 10:38
Цитата JoeUser @
Так можно с буфером I/O поиграться:

Зачем? Твой пример - из конца 90х - начала 2000х, это как минимум небезопасный и морально устаревший код. С кучей подводных камней.
Я вообще считаю что за raw pointer'ы по рукам нужно бить, а уж за new - темболее.
Плюс ко всему нужно вставлять ненужные проверки на то что хватило ли буфера и все это еще оборачивать мьютексами, имхо это очень опасный код ты написал для многопоточной среды.
У меня же по сути используется такой же буфер, но безопасный в плане ошибок и многопоточный.

Добавлено
У меня же по сути сбрасывается в файл примерно так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
     std::ofstream m_fout; //член класса Logger
     
    Logger::Logger
    {
       m_fout.open(filename)
    }
     
    Logger::~Logger
    {
       m_fout.close()
    }
     
    void flush()
    {
       while(queue.size() > 0)
       {
          std::wstring message;
          if(queue.try_pop(message()))
             fout << message; // <<<<<< тут фактически выводятся данные в файловый поток
       }
       fout.flush(); //! <<<<< вот тут фактически осуществляется запись в файл
    }

Автор: JoeUser 14.07.18, 11:34
Цитата Wound @
Зачем? Твой пример - из конца 90х - начала 2000х, это как минимум небезопасный и морально устаревший код.

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

Добавлено
ЗЫ: Хотя ладно, как говорят - на нет и суда нет. Я попытался :lol:

Автор: Wound 14.07.18, 11:46
Цитата JoeUser @
Ноу комментс Киля, ну не нравится мой код - в доку хоть загляни, почитай как там описана работа буферизации. А то делаешь поспешные выводы не разобравшись.

Ок, давай перефразирую: так а смысл мне писать из разных потоков в один файл? Зачем мне так делать? Какой профит я получу от этого? Я этого не понимаю.
Плюс ко всему код что ты привел - очень опасный для многопоточной среды.
У тебя получается что std::ofstream ofs("test.txt", std::ofstream::out); нужно защищать мьютексом. Зачем ?
Сейчас у меня локальная переменная std::wostingstream - для каждого потока, они не конкурируют за этот поток(std::wostingstream), а каждый пишет в свой поток, а потом сбрасывают это содержимое в общуюю очередь, и им плевать что там дальше происходит с этой очередью, об этом заботится кто то другой. И работа с файлом идет ровно в одном потоке.
Как по мне это дает больше гарантий целосности данных + больший контроль над состоянием файла. Разве я ошибаюсь?

Автор: JoeUser 14.07.18, 12:56
Цитата Wound @
Ок, давай перефразирую: так а смысл мне писать из разных потоков в один файл? Зачем мне так делать? Какой профит я получу от этого? Я этого не понимаю.

Ланна, объясню по шагам, как я это понимаю.

  • Максимальную скорость записи можно получить при записи в один поток и один файл (диск по минимуму дергает позиционирование считывающих головок, если он не SDD)
  • Библиотечная буферизация работает отлично, не надо ничего контролить, переполнений не будет
  • Если увеличить буфер для потока, допустим до 8 или 16 mb, мелкие строки будут влетать в поток только в путь, без фактической записи на диск
  • Размер буфера я бы считал исходя из аппаратного буфера HDD, чтобы чисто скинули нужный объем, и HDD "сказал" сразу мол "я записал" ... а сам еще пишет
  • При наличии достаточного размера буфера - ты из разных threads, по факту, будешь писать в буфер потока, а не сразу в файл
  • Использование средств синхронизации обеспечит безопасное использование потоков при многопоточном способе доступа к ним

Чисто ИМХО. Идеальным моментом сброса буфера (flush) будет момент, когда буфер заполнится на 3/4 или 5/7 от объема HDD. Естественно, этот функционал нужно по-хорошему реализовать в твоем логере. Произвести тестовые испытания.

Я поэтому и писал выше:

Цитата JoeUser @
Но я бы не трогал стримы в плане организации их эффективного кэширования - а завел бы отдельный класс, который был бы манагером кэширования.


Ну вот как-то так.

Добавлено
Цитата Wound @
Плюс ко всему код что ты привел - очень опасный для многопоточной среды.
У тебя получается что std::ofstream ofs("test.txt", std::ofstream::out); нужно защищать мьютексом. Зачем ?


Обязательно нужно!
Многопоточный доступ к стримам без ошибок Стандарт С++ не гарантирует.
Пока почитать негде - Qraizer'а попроси, если нужен железный пруф.

Добавлено
Повторю свой код и поставлю комменты по синхронизации:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        #include <iostream>
        #include <fstream>
        
        using namespace std;
        
        class LoggedStream {
          private:
            ostream& out;
          public:
            LoggedStream(ostream& o):out(o){}
            template<typename T>
            const LoggedStream& operator<<(const T& v) const {
              // начало синхронизации
              out << v;
              // завершение синхронизации
              return *this;
            }
            LoggedStream const& operator<<(std::ostream& (*func)(std::ostream&)) const {
              // начало синхронизации
              func(out);
              // завершение синхронизации
              return *this;
            }
        };
        
        int main(int,char**) {
          LoggedStream Log1(std::cout);
          Log1 << 1 << " 2" << endl << 3 << " 4" << endl;
          //
          std::ofstream ofs("test.txt", std::ofstream::out);
          LoggedStream Log2(ofs);
          Log2 << 1 << " 2" << endl << 3 << " 4" << endl;
        }

Автор: Wound 14.07.18, 13:57
Цитата JoeUser @
Максимальную скорость записи можно получить при записи в один поток и один файл (диск по минимуму дергает позиционирование считывающих головок, если он не SDD)

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

Цитата JoeUser @
Обязательно нужно!
Многопоточный доступ к стримам без ошибок Стандарт С++ не гарантирует.

Это я знаю. Это же было утверждение, а не вопрос. Вопрос был - Зачем мне это? Там перед "Зачем?" - точка стоит.

Цитата JoeUser @
Повторю свой код и поставлю комменты по синхронизации:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ...
    const LoggedStream& operator<<(const T& v) const {
              // начало синхронизации
              out << v;
              // завершение синхронизации
              return *this;
            }
            LoggedStream const& operator<<(std::ostream& (*func)(std::ostream&)) const {
              // начало синхронизации
              func(out);
              // завершение синхронизации
              return *this;
            }
    ...


У тебя тут уже ошибка, ты получил race condition. А мне это зачем? Понимаешь - именно по этому я и считаю, что плохая идея работать с одним файлом из разных потоков. Именно по этой причине - я тут изгалялся с condition variable внутри манипулятора. А ты совершил ровно ту же ошибку, которую я тут пытаюсь разрешить. Поэтому мне видится код Олег М более приемлемым. Потому как там не возникает состояния гонки потоков. А потом сформированное сообщение безопасно помещается в потокобезопасную очередь - читай кэш.
И по достижении определенного размера кэш сбрасывается в файл на диск.

Автор: JoeUser 14.07.18, 14:33
Цитата Wound @
Потому как там не возникает состояния гонки потоков.

Походу ты малеха путаешь термин "гонка". Гонки не будет.
Будет конкуренция, и это в многопоточном приложении - нормально.

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

Автор: Wound 14.07.18, 14:55
Цитата JoeUser @
Походу ты малеха путаешь термин "гонка". Гонки не будет.
Будет конкуренция, и это в многопоточном приложении - нормально.

Да нет, не путаю:
Состояние гонки
Цитата
Состояние гонки (англ. race condition), также конкуренция[1

Дело в том, что я еще не понял - понял ли ты сам свою ошибку или нет. Но, я на всякий случай тебе объясню:
На самом деле у тебя твой пример будет работать не так, как ты думаешь, вот тут:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    LoggedStream Log1(std::cout);
     
          Log1 << 1 << " 2" << endl << 3 << " 4" << endl; //! <<<<<<< Вот в этой строчке

На самом деле у тебя аж 6(!) вызовов разных методов класса LoggedStream, и вызовы эти не последовательны, а хаотичны.
В итоге если ты эту строчку напишешь в разных потоках, то получишь не две строчки:
Цитата thread1
1 2
3 4

Цитата thread2
1 2
3 4

А что то совсем другое. Как пример:
Цитата thread1
1 1
4 2

Цитата thread2
2 3
3 4

А это наверное не то, что ты ожидал. У тебя несколько потоков будут в состоянии гонки за переменную std::cout.
И это не нормально.
Щас я твой пример перепишу чтоб он работал, покажу на наглядном примере.

Автор: JoeUser 14.07.18, 15:15
Wound, все понял. Писать не надо.
Да, ты прав - гонка будет в случае более одного '<<'.

Автор: Wound 14.07.18, 15:24
Вот попробуй выполнить у себя этот код.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <iostream>
    #include <mutex>
    #include <thread>
    #include <chrono>
     
    using namespace std::chrono_literals;
     
    class LoggedStream {
    private:
        std::ostream& out;
        mutable std::mutex m_mutex;
    public:
        LoggedStream(std::ostream& o) : out(o) {}
        template<typename T>
        const LoggedStream& operator<<(const T& v) const {
            std::lock_guard<std::mutex> guard(m_mutex); //! Начало синхронизации, завершение синхронизации в деструкторе, после выхода из этой функции!
            // начало синхронизации
            out << v;
            // завершение синхронизации
            return *this;
        }
        LoggedStream const& operator<<(std::ostream& (*func)(std::ostream&)) const {
            std::lock_guard<std::mutex> guard(m_mutex); //! Начало синхронизации, завершение синхронизации в деструкторе, после выхода из этой функции!
            // начало синхронизации
            func(out);
            // завершение синхронизации
            return *this;
        }
    };
     
    void task1(LoggedStream& stream)
    {
        auto old = std::chrono::steady_clock::now();
        while (true)
        {
            auto now = std::chrono::steady_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - old).count();
            if (elapsed > 3)
                break;
            stream << "The " << " quick " << " brown " << " fox " << " jumps " << " over " << " the " << " lazy " << " dog " << std::endl;
        }
    }
     
    void task2(LoggedStream& stream)
    {
        auto old = std::chrono::steady_clock::now();
        while (true)
        {
            auto now = std::chrono::steady_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - old).count();
            if (elapsed > 3)
                break;
            stream << "This " << " is " << " message " << " from " << " the " << " second " << " thread " << " olo" << "lo " << std::endl;
        }
    }
     
    int main()
    {
        LoggedStream Log1(std::cout);
     
        std::thread tr1(task1, std::ref(Log1));
        std::thread tr2(task2, std::ref(Log1));
     
        tr1.join();
        tr2.join();
     
        std::cin.get();
        return 0;
    }

Допустим у меня на компиле, как вариант, я получил:
Цитата

The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox This is message from the second thread ololo
jumps over the lazy dog
This is message from the second thread ololo
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
This is message from the second thread ololo
The quick brown fox jumps over the lazy dog
This is message from the second thread ololo
The quick brown fox jumps over the lazy dog
This is message from the second thread ololo
The quick brown fox jumps over This is message from the second thread
ololo
the lazy dog
This is message from the second thread ololo
The quick brown fox jumps over the lazy dog
This is message from the second thread ololo
This is message from the second thread ololo
The quick brown fox jumps over the lazy dog
This is message from the The quick brown fox jumps over the lazy dog

The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog

Это ли не race condition ? :-?

Автор: JoeUser 14.07.18, 15:54
Wound, киля, да да да, ты прав!
Пока осознанного решения для вызовов а-ля "<< ололо << ололо << ололо << ололо" без гонки я не вижу.
Тут вопрос принципиальный, как распознать "конец цепочки вызовов".
У меня такого решения пока нет :-?

Автор: Wound 14.07.18, 16:01
Цитата JoeUser @
В пока осознанного решения для вызовов а-ля "<< ололо << ололо << ололо << ололо" без гонки я не вижу.

Ну одно ты забраковал, то, которое я привел с манипулятором, без вызова которого - все уйдет в режим ожидания.
Второе привел Oleg M
Я его даже уже реализовал. Идея в том, что в каждом потоке ты пишешь в локальный поток, который возвращает логгер, а потом скидываешь все в этот самый логгер.
Второе решение кажется мне более красивым, чем мое. Но я свое делал, потому что обиделся на манипулятор :D Уже ради принципе там извращался.

Цитата JoeUser @
Тут вопрос принципиальный, как распознать "конец цепочки вызовов".
У меня такого решения пока нет

Вот как вариант - с помощью conditional_variable, но тогда без вызова flush, все заблокируется.
Либо писать в локальную переменную Ну или как Qraizer предлагал, либо смотри мой первый пост.
Смысл в том, чтобы блокировать всю цепочку вызовов - operator << и писать ее во временную переменную, а уж потом ее возвращать.

Автор: Qraizer 14.07.18, 16:03
Цитата Wound @
Ну или как Qraizer предлагал, либо смотри мой первый пост.
Qraizer вообще-то твой исходный код, без смены архитектуры, подправил уже давно.

Автор: JoeUser 14.07.18, 16:08
Wound, не не не.
Вопрос в другом!
Который можно обозначить так: "Как обозначить цепочку вызовов записи в стрим - как законченную 'транзакцию'?"

Лично я пока вижу один выход - собирать как-то "сообщение" из всех "<<" воедино и отдавать одним вызовом.
На большее - креативу не хватает :'(

Автор: Wound 14.07.18, 16:11
Цитата Qraizer @
Qraizer вообще-то твой исходный код, без смены архитектуры, подправил уже давно.

Да, я знаю, я видел. Но ведь не удобно писать Guard(logger) << message.

Автор: JoeUser 14.07.18, 16:30
Цитата Wound @
Да, я знаю, я видел. Но ведь не удобно писать Guard(logger) << message.

Сорри, убегаю и не осознал ... но чисто вопрос.

В случае Guard(logger) << message1 << message2 << message3

Есть ли механизмы предотвращения гонки? И если "да" - это где?

Автор: Wound 14.07.18, 16:38
Цитата JoeUser @
Есть ли механизмы предотвращения гонки? И если "да" - это где?

Да, там есть такой механизм, вот Qraizer его описал:
Цитата Qraizer @
Ну да, перемудрил, пожалуй. Тебе было надо лишь залочить логгер до конца выражения. Для этого достаточно временного Guard, лочащего агрегированный в логгер std::basic_ostream, и отпускающего его в деструкторе, а сами << пусть бы работали сами по себе, стандартные. И не надо было бы никаких враперов.

Просто в моем первом варианте нужно выкинуть WrappedStream, и писать примерно так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    GuardStream(std::cout) << message1 << message2 << std::endl;

Только там у GuardStream нужно operator << перегрузить. Получится что у тебя в момент вызова GuardStream(std::cout) создается локальный временный объект GuardStream, который в конструкторе лочит std::cout, а в деструкторе отпускает его и вызывает flush.
Ну или готовое решение смотри по ссылке, что привел Qraizer: Маразм программёров

Автор: Qraizer 14.07.18, 18:03
Цитата Wound @
Да, я знаю, я видел. Но ведь не удобно писать Guard(logger) << message.
Нет, ты не увидел.

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

Добавлено
https://ideone.com/0j423Q

Автор: Wound 14.07.18, 18:35
Цитата Qraizer @
Нет, ты не увидел.

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

Цитата Qraizer @
Ты просил реализацию манипулятора, я предложил сменить архитектуру. и не только я.

Я тоже предлагал сменить архитектуру.
Цитата Wound @
Передал ему ссылку на Logger, в итоге выхватил исключение при попытке залочить мьютекс. Может быть тут можно как то дизайн пересмотреть, а то у меня голова уже не варит, с 3 ночи сижу пытаюсь этот манипулятор написать. Мне кажется я уже все варианты перепробовал.




Цитата Qraizer @
но конкретно твой вопрос по манипулятору решён.

Добавлено 9 минут назад
https://ideone.com/0j423Q

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

Автор: Qraizer 14.07.18, 22:01
Цитата Wound @
Видимо как раз там и нужно было изменить на рекурсивный мьютекс.
Вообще говоря, если используешь std для лочки расшаренных ресурсов, имеет смысл всегда использовать именно их, т.к. точно соответствуют критическим секциям по назначению. Обычные мьютексы соответствуют виндовым семафорам со счётчиком 2, что требуются гораздо реже.

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