Как мне реализовать удобную регистрацию без Singleton? - PullRequest
37 голосов
/ 01 декабря 2011

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

#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 счастлив
  • относительно простая, легкая для понимания реализация

Однако минусами являются:

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

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

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

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

Ответы [ 4 ]

40 голосов
/ 01 декабря 2011

Первый: использование std::unique_ptr не нужно:

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

Создает точно такую ​​же ленивую семантику инициализации и очистки, не вводя весь синтаксический шум (и избыточный тест).

Теперь это не так ...

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

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

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

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

// 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, это нормально, см. Этот вопрос

12 голосов
/ 01 декабря 2011

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

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

Итак, нам нужно что-то , чтобы быть глобально доступным.

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

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

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

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

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

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

11 голосов
/ 01 декабря 2011

Мне очень нравится следующий интерфейс, поскольку он использует потоковую передачу. Конечно, вы можете добавить каналы, время и поток информации к нему. Другим возможным расширением является использование макросов __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;
}
0 голосов
/ 03 апреля 2014
// 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...