На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
Модераторы: JoeUser, Qraizer, Hsilgos
Страницы: (2) [1] 2  все  ( Перейти к последнему сообщению )  
> Можно ли как то сделать вариативный шиблонный метод виртуальным, или написать прокси метод для реализации дин. полиморфизма с использованием variadic templates ?
Всем привет!

Вот как сделать что то подобное? И можно ли такое сделать?
Суть в чем, мне нужно прикрутить логирование. Но оно может писаться например в лог файл, а может в журнал событий винды.
Я хочу сделать два класса, один пишет в файл, второй пишет в журнал событий.
И завести общий интерфейc ILogger, через который можно было бы логировать либо в файл, либо в журнал событий.
Для удобства логирования я перегрузил operator << в своем классе, который пишет в лог, и примерно так же, в идеале я хотел бы написать в своем интерфейсе. Но так как у меня шаблонный оператор вывода в поток, сделать его виртуальным не получится.
Тогда я решил пойти другим путем, в интерфейсе сделать виртуальным метод virtual void PrintMessage(const std::string& format, ...) = 0; - обычный метод с переменным числом параметров.
но не могу понять как его реализовать(и можно ли) так, чтобы передать потом все это дело во внутрений вариативный шаблонный метод?

ExpandedWrap disabled
    class ILogger
    {
    public:
        virtual void PrintMessage(const std::string& format,  ...) = 0;
    };
     
    class ConcreteTxt : public ILogger
    {
    public:
        void PrintMessage(const std::string& format, ...) override
        {
            internal_print(format.c_str(), "", ???); //! <<<<<<< ????
        }
     
        template<typename T, typename ... Args>
        void internal_print(const char* format, T value, Args ... args)
        {
            for (; *format != '\0'; format++)
            {
                if (*format == '%')
                {
                    std::cout << value;
                    internal_print(format + 1, args ...);
                    return;
                }
                std::cout << *format;
            }
        }
    };


Спасибо.
Сообщение отредактировано: Wound -
Лучше сделай наоборот, чтобы internal_print вызывал виртуальную PrintMessage(const std::string& msg).

А ещё лучше сделай класс Message, который будет отвечать за форматирование сообщении, и Logger с виртуальным методом, который будет сохранять сообщение
Т.е. раздели задачи форматирования и сохранения сообщений. Что-то типа
ExpandedWrap disabled
    class ILogger
    {
    public:
        void Flush(const std::stringstream &msg) noexcept = 0;
    };
     
    class CMessage
    {
    public:
        CMessage(ILogger &);
        ~CMessage()
        {
            m_logger.Flush(m_msg);
        }
     
        operator <<(TT&& val)
        {
            m_msg << val;
        }
    protected:
        ILogger &m_logger;
        std::stringstream m_msg;
    };
     
    .......
    class CFileLogger: public ILogger......
    class CSystemLogger: public ILogger......
     
    CFileLogger  log;
     
    CMessage(log) << "!!!!!";
Сообщение отредактировано: Олег М -
Но тогда я начинаю зависеть от двух классов. Мне интерфейс логгирования нужно будет передавать в конструктор другого класса, который ничего не знает о том, какие там логеры вообще есть, он просто у интерфейса будет дергать метод логирования и все.
Ты в любом случае будешь зависеть от двух классов, как ни крути. В данном случае у тебя реализация форматирования не будет зависеть от реализации сохранения. Классу Message не нужно знать как будет сохранено то в нем хранится.

Как вариант можно сделать так
ExpandedWrap disabled
    class CLogger
    {
    public:
     
        template <typename... TT>
        void Print(TT&&... args)
        {
            std::stringstream out;
            out << args......
     
            Flush(out);
        }
        
    protected:
        virtual void Flush(const std::stringstream &) = 0;
    };
     
    class CFileLogger: public CLogger......
    class CSystemLogger: public CLogger......
     
    CFileLogger log1;
    CSystemLogger log2;
     
    log1.Print(1, 2, 3);
    log2.Print(1, 2, 3);

Но это менее гибкое решение
Ладно, я кажется понял. Там с первым вариантом что ты предложил можно будет поизвращаться.
В принципе такой вариант будет приемлемым.
Спасибо за примеры.
Цитата Олег М @
Мне интерфейс логгирования нужно будет передавать в конструктор другого класса, который ничего не знает о том, какие там логеры вообще есть, он просто у интерфейса будет дергать метод логирования и все.

Кстати в метод Flush лучше передавать не stringstream а Message Flush(const CMessage &). Тогда ты сможешь сохранять не только текст, но и параметры сообщения - тип, поток, время и т.д.
Цитата Олег М @
Что-то типа

Сама запись в лог может быть ошибочна, а в таком
варианте об этом никак не узнаешь.
CMessage не обязателен. "ILogger &m_logger;" может находиться в составе любых объектов

я бы как то так сделал:
ExpandedWrap disabled
        class ILogger
        {
        public:
            bool write (const TCHAR* pFmt,...) = 0;
        };
        
        class CSomeClass
        {
        public:
            CSomeClass(ILogger &);
            ~CSomeClass()
            {
            }
        
        bool  SomeRoutine (const TCHAR* pFmt,...)
        {
    ...
    ...
         if(надо_в лог)
         {
          m_logger.write (_T("xxmm=%d\r\n"),xxmm);
         }
    ...
    ...
        }
        protected:
            ILogger &m_logger;
        };
        
        .......
        class CFileLogger: public ILogger......
        class CSystemLogger: public ILogger......
        
        CFileLogger  log;
        
        CSomeClass obj(log);


Добавлено
Цитата Олег М @
Тогда ты сможешь сохранять не только текст, но и параметры сообщения - тип, поток, время и т.д.

А это может быть определено в классах:
ExpandedWrap disabled
    class CFileLogger: public ILogger......
    class CSystemLogger: public ILogger......
Подпись была выключена в связи с наложенным заземлением.
Цитата ЫукпШ @
Сама запись в лог может быть ошибочна, а в таком
варианте об этом никак не узнаешь.

Может. И что ты в этом случае делаешь?
Лично я ничего, просто пропускаю, все методы у CMessage помечены как noexcept - запись в лог не должна влиять на вызывающий поток.

Добавлено
Цитата ЫукпШ @
    class ILogger
    {
    public:
        bool write (const TCHAR* pFmt,...) = 0;
    };

Во-первых, лучше не использовать ... без крайней на то необходимости. А в современном с++ такая необходимость не возникает никогда.
Во-вторых, повторяюсь - при логировании есть две задачи - форматирование и сохранение, не нужно их мешать в кучу
Цитата Олег М @
Может. И что ты в этом случае делаешь?

Так это зависит от логгера.
Если логируем в сеть, так можно об этом и в лог-файл сообщить.
А так лучше, чем ничего:
ExpandedWrap disabled
        if(надо_в лог)
         {
          if(!m_logger.write (_T("xxmm=%d\r\n"),xxmm))
          {
           ::OutputDebugString(...);
          }
         }

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

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

Окончательное форматирование делает объект-логгер.
Поскольку интересующая информация зависит от способа логгирования.
Напрмер, при логе в файл IP адрес не интересует.
Форматирование к моему примеру имеет отдалённое отношение.
Это всего лишь удобство произвольного вывода для произвольной точки
любого приложения. В самом общем виде.
Подпись была выключена в связи с наложенным заземлением.
Цитата ЫукпШ @
А так лучше, чем ничего:

    if(надо_в лог)
     {
      if(!m_logger.write (_T("xxmm=%d\r\n"),xxmm))
      {
       ::OutputDebugString(...);
      }
     }

Вообще, хуже. Лучше, чем ничего - это если этот код будет находиться внутри m_logger.write.



Цитата ЫукпШ @
Окончательное форматирование делает объект-логгер.

Нет, не делает. Логгер добавляет какую-то сопутствующую информацию в нужном формате, необходимую для сохранения сообщения. Собственно строку сообщения он не трогает.
Например для сохранения в системный лог он, логгер, заполняет поля соответствующей структуры. Можно, конечно, назвать это "окончательным форматированием", но только с очень большой натяжкой.
Цитата Олег М @
Цитата ЫукпШ @
А так лучше, чем ничего:

    if(надо_в лог)
     {
      if(!m_logger.write (_T("xxmm=%d\r\n"),xxmm))
      {
       ::OutputDebugString(...);
      }
     }

Вообще, хуже. Лучше, чем ничего - это если этот код будет находиться внутри m_logger.write.



Цитата ЫукпШ @
Окончательное форматирование делает объект-логгер.

Нет, не делает. Логгер добавляет какую-то сопутствующую информацию в нужном формате, необходимую для сохранения сообщения. Собственно строку сообщения он не трогает.
Например для сохранения в системный лог он, логгер, заполняет поля соответствующей структуры. Можно, конечно, назвать это "окончательным форматированием", но только с очень большой натяжкой.

Конечно нет.
Это просто пример возможного.
..я вставлю это в "m_logger.write", но "OutputDebugString"
не во всех системах и не во всех приложениях работает.
В win7 в системных службах это работать не будет, а в XP - будет.
В Linux этого вообще нет.
Приведённый пример - не универсальный вариант.
---
Возвращаемый результат работы функции - это всегда лучше. По определению.
При любой задаче и любом контексте происходящего.
Если не возвращается результат, это предмет для разбирательства.
Сообщение отредактировано: ЫукпШ -
Подпись была выключена в связи с наложенным заземлением.
Во всей вашей шляпе хотелось бы видеть нормальное, прозрачное (а лучше, замаскированное) использование отладочных "величин" __FILЕ__ и __LINE__. Но, пока это не сильно представляется возможным.
Мои программные ништякиhttp://majestio.info
Цитата JoeUser @
Во всей вашей шляпе хотелось бы видеть нормальное, прозрачное (а лучше, замаскированное) использование отладочных "величин" __FILЕ__ и __LINE__. Но, пока это не сильно представляется возможным.

А зачем они вообще нужны? :huh:
Во первых с ними нужно возиться, потому как это макросы. И в большинстве случаев информация не будет соответствовать действительности. Типа вылетел по исключению в какой нибудь верхнеуровневый catch, и уже там записал в лог файл неверную инфу. Но если тебе очень хочется, то почему прям в лог не написать __FILE__ и __LINE__ ?
Цитата Wound @
А зачем они вообще нужны?

Только в отладочных целях. Знать, что во время выполнения произошло что-то, и что оно в таком-то месте, такого-то исходника.

Цитата Wound @
Но если тебе очень хочется, то почему прям в лог не написать __FILE__ и __LINE__ ?

Да, пока я вижу это единственный вариант. Поэтому и спросил.
Мои программные ништякиhttp://majestio.info
Цитата JoeUser @
Только в отладочных целях. Знать, что во время выполнения произошло что-то, и что оно в таком-то месте, такого-то исходника.

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

Цитата JoeUser @
Да, пока я вижу это единственный вариант. Поэтому и спросил.

Ну я лично пока не вижу необходимости для себя в этих макросах, поэтому и не заморачивался особо.
Ну и по сути других вариантов нет. Это же макросы, они раскроются сразу же после препроцессора, перед компиляцией. А это значит я и так могу знать где у меня произошла ошибка. Достаточно поискать по тексту ошибки :-?
Сообщение отредактировано: Wound -
1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
0 пользователей:


Рейтинг@Mail.ru
[ Script Execution time: 0,1573 ]   [ 20 queries used ]   [ Generated: 19.04.19, 06:53 GMT ]