Подтвердить что ты не робот

Как реализовать удобную регистрацию без Singleton?

Моя текущая реализация, упрощенная:

#include <string>
#include <memory>

class Log
{
  public:
    ~Log() {
      // closing file-descriptors, etc...
    }
    static void LogMsg( const std::string& msg )
    {
      static std::unique_ptr<Log> g_singleton;
      if ( !g_singleton.get() )
        g_singleton.reset( new Log );
      g_singleton->logMsg( msg );
    }
  private:
    Log() { }
    void logMsg( const std::string& msg ) {
      // do work
    }
};

В общем, я удовлетворен этой реализацией, потому что:

  • ленивый экземпляр означает, что я не плачу, если я его не использую
  • Использование unique_ptr означает автоматическую очистку, поэтому valgrind счастлив.
  • относительно простая, понятная реализация

Однако отрицательные стороны:

  • синглеты не способствуют единичному тестированию
  • диссонанс в глубине моего сознания для введения псевдоглобального (немного запаха кода)

Итак, вот мои вопросы, направленные на тех разработчиков, которые успешно изгоняют все синглтоны из своего кода на С++:

  • Какую не-синглтонскую реализацию вы используете для ведения журнала приложений?
  • Является ли интерфейс простым и доступным как вызов Log:: LogMsg() выше?

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

4b9b3361

Ответ 1

Во-первых: использование std::unique_ptr не требуется:

void Log::LogMsg(std::string const& s) {
  static Log L;
  L.log(s);
}

Производит точно такую ​​же ленивую семантику инициализации и очистки без введения всего синтаксического шума (и избыточного теста).

Теперь это не в порядке...

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

  • Отметка времени
  • уровень
  • файл
  • строка
  • Функция
  • имя процесса/идентификатор потока (если необходимо)

поверх самого сообщения.

Таким образом, вполне возможно иметь несколько объектов с разными параметрами:

// LogSink is a backend consuming preformatted messages
// there can be several different instances depending on where
// to send the data
class Logger {
public:
  Logger(Level l, LogSink& ls);

  void operator()(std::string const& message,
                  char const* function,
                  char const* file,
                  int line);

private:
  Level _level;
  LogSink& _sink;
};

И вы обычно закрываете доступ внутри макроса для удобства:

#define LOG(Logger_, Message_)                  \
  Logger_(                                      \
    static_cast<std::ostringstream&>(           \
      std::ostringstream().flush() << Message_  \
    ).str(),                                    \
    __FUNCTION__,                               \
    __FILE__,                                   \
    __LINE__                                    \
  );

Теперь мы можем создать простой подробный журнал:

Logger& Debug() {
  static Logger logger(Level::Debug, Console);
  return logger;
}

#ifdef NDEBUG
#  define LOG_DEBUG(_) do {} while(0)
#else
#  define LOG_DEBUG(Message_) LOG(Debug(), Message_)
#endif

И используйте его удобно:

int foo(int a, int b) {
  int result = a + b;

  LOG_DEBUG("a = " << a << ", b = " << b << " --> result = " << result)
  return result;
}

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

Примечание: если бит магии с участием std::ostringstream пугает вас, это нормально, см. этот вопрос

Ответ 2

Я бы пошел с простым, прагматичным решением:

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

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

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

Итак, сделайте его глобальным. Простая старая простая глобальная переменная.

Это все еще не полностью решает проблему с модульным тестированием, правда, но у нас не всегда есть все, что мы хотим.;)

Как указано в комментарии, вам нужно рассмотреть порядок инициализации для глобальных переменных, который на С++ частично undefined.

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

Но это то, что вам нужно учитывать, по крайней мере.

Ответ 3

Мне очень нравится следующий интерфейс, поскольку он использует потоковое вещание. Конечно, вы можете добавить к нему каналы, время и информацию о потоках. Другим возможным расширением является использование макросов __FILE__ и __LINE__ и добавление его в качестве параметров для конструктора. Вы даже можете добавить функцию вариационного шаблона, если вам не нравится синтаксис потока. Если вы хотите сохранить некоторую конфигурацию, вы можете добавить их к некоторым статическим переменным.

#include <iostream>
#include <sstream>

class LogLine {
public:
    LogLine(std::ostream& out = std::cout) : m_Out(out) {}
    ~LogLine() {
        m_Stream << "\n";
        m_Out << m_Stream.rdbuf();
        m_Out.flush();
    }
    template <class T>
    LogLine& operator<<(const T& thing) { m_Stream << thing; return *this; }
private:
    std::stringstream m_Stream;
    std::ostream& m_Out;
    //static LogFilter...
};

int main(int argc, char *argv[])
{
    LogLine() << "LogLine " << 4 << " the win....";
    return 0;
}

Ответ 4

// file ILoggerImpl.h 

struct ILoggerImpl
{
    virtual ~ILoggerImpl() {}
    virtual void Info(std::string s) = 0;
    virtual void Warning(std::string s) = 0;
    virtual void Error(std::string s) = 0;
};


// file logger.h //
#include "ILoggerImpl.h"

class CLogger: public ILoggerImpl
{
public:
    CLogger():log(NULL) {  }

    //interface
    void Info(std::string s)  {if (NULL==log) return; log->Info(s); }
    void Warning(std::string s) {if (NULL==log) return; log->Warning(s); }
    void Error(std::string s) {if (NULL==log) return; log->Error(s); }


    //
    void BindImplementation(ILoggerImpl &ilog) { log = &ilog; }
    void UnbindImplementation(){ log = NULL; }


private:
    ILoggerImpl *log;
};


// file: loggers.h //

#include "logger.h"
extern CLogger Log1;
extern CLogger Log2;
extern CLogger Log3;
extern CLogger Log4;
extern CLogger LogB;



/// file: A.h //
#include "loggers.h"  

class A
{

public:
    void foo()
    {
        Log1.Info("asdhoj");
        Log2.Info("asdhoj");
        Log3.Info("asdhoj");

    }
private:

};


/// file: B.h //
#include "loggers.h"

class B
{

public:
    void bar()
    {
        Log1.Info("asdhoj");
        Log2.Info("asdhoj");
        LogB.Info("asdhoj");
        a.foo();
    }



private:

    A a;
};



////// file: main.cpp  ////////////////


#include "loggers.h"
#include "A.h"
#include "B.h"
#include "fileloger.h"
#include "xmllogger.h"

CLogger Log1;
CLogger Log2;
CLogger Log3;
CLogger Log4;
CLogger LogB;

// client code

int main()
{
    std::unique_ptr<ILoggerImpl> filelog1(new CFileLogger("C:\\log1.txt"));
    Log1.BindImplementation(*filelog1.get());

    std::unique_ptr<ILoggerImpl> xmllogger2(new CXmlLogger("C:\\log2.xml"));
    Log2.BindImplementation(*xmllogger2.get());

    std::unique_ptr<ILoggerImpl> xmllogger3(new CXmlLogger("C:\\logB.xml"));
    LogB.BindImplementation(*xmllogger3.get());


    B b;
    b.bar();



    return 0;
};



// testing code
///////file: test.cpp /////////////////////////////////

#include "loggers.h"
CLogger Log1;
CLogger Log2;
CLogger Log3;
CLogger Log4;

int main()
{
    run_all_tests();
}



///////file: test_a.cpp /////////////////////////////////

#include "A.h"

TEST(test1)
{
    A a;
}

TEST(test2, A_logs_to_Log1_when_foo_is_called())
{
    A a;
    std::unique_ptr<ILoggerImpl> filelog1Mock(new CFileLoggerMock("C:\\log1.txt"));
    Log1.BindImplementation(*filelog1.get());
    EXPECT_CALL(filelog1Mock  Info...);

    a.foo();
    Log1.UnbindImplementation();
}