На главную
ПРАВИЛА 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 -
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script Execution time: 0,1639 ]   [ 18 queries used ]   [ Generated: 18.07.19, 13:44 GMT ]