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

С++: класс логгера без глобалов или синглетов или передача его каждому методу

Кто-нибудь знает, возможно ли иметь такой класс, как регистратор:

  • с использованием одноэлементного или глобального (a la std:: cout)

  • передача экземпляра/указателя/ссылки на каждый способ, который ему нужен

Я беру пример класса logger, но у меня есть несколько классов в моем приложении, которые выиграют от этого (например, менеджер отмены).

В каждом решении есть несколько проблем:

  • использование синглтона проблематично для тестирования (наряду со многими причинами, по которым не рекомендуется использовать его). Это то же самое с глобальным. Кроме того, ничто не гарантирует, что в приложении будет только один экземпляр, и это даже не требование (почему, например, нет 2 регистраторов?)

  • передача его каждому конструктору объекта (инъекция зависимостей), приводит к большому шаблону кода и может быть подвержена ошибкам, потому что вам приходится копировать/вставлять один и тот же код много раз. Можно ли серьезно подумать о наличии указателя на Logger в каждом конструкторе каждого класса???????

Итак, мне было интересно, есть ли в С++ третий вариант, о котором я никогда не слышал? Для меня это похоже на то, что под капюшоном потребуется черная магия, но я был приятно удивлен некоторыми методами, которые я узнал в переполнении стека, которые я не мог найти в google, поэтому я знаю, что здесь есть настоящие гуру;)

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

4b9b3361

Ответ 1

Я бы предположил, что вы можете сделать что-то похожее на то, что сделано на Java с пакетом Log4j (и, вероятно, это сделано с его версией Log4c):

Имейте статический метод, который может возвращать несколько экземпляров журнала:

Logger someLogger = Logger.getLogger("logger.name");

Метод getLogger() не возвращает одноэлементный объект. Он возвращает именованный регистратор (при необходимости создавая его). Logger - это просто интерфейс (в С++ он может быть полностью абстрактным классом) - вызывающему не нужно знать фактические типы создаваемых объектов Logger.

Вы могли бы продолжать имитировать Log4j и иметь перегрузку getLogger(), которая также принимает объект factory:

Logger someLogger = Logger.getLogger("logger.name", factory);

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

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

Ответ 2

Как насчет класса с некоторыми статическими методами?

class Logger
{
public:
  static void record(string message)
  {
    static ofstream fout("log");
    fout << message << endl;;
  }
  ...
};

...

void someOtherFunctionSomewhere()
{
  Logger::record("some string");
  ...
}

Нет Singleton, нет глобальной переменной, но любой код, который может видеть Logger.h, может вызвать функцию-член, а если все функции public member возвращаются void, это тривиально легко заглушить для тестирования.

Ответ 3

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

// Pseudo-C++ with dynamic binding
Logger logger = Logger("GlobalLogger");

void foo() { logger.log("message"); }

int main() 
{
   foo(); // first
   {
      Logger logger = Logger("MyLogger");
      foo(); // second
   }
   foo(); // third
}

В этом примере псевдо-С++, когда вы вызываете foo() в первый раз, GlobalLogger будет использоваться, когда вы его вызове второй раз, MyLogger будет вызываться вместо этого, так как MyLogger будет переопределять глобальную переменную до тех пор, пока он находится в области видимости даже внутри foo(). Третий вызов вернется к GlobalLogger, так как MyLogger выпадает из сферы действия. Это позволяет переопределить глобальный регистратор с помощью настраиваемого кода для отдельных фрагментов кода без необходимости пропускать объект журнала через весь код и не устанавливать глобальную переменную.

Real С++ не имеет динамического связывания, но он должен иметь возможность реплицировать его аспекты:

std::stack<Logger> logger = { Logger("GlobalLogger") };

void foo() { logger.top().log("message"); }

int main()
{
    foo();
    {
      logger.push(Logger("MyLogger"));
      foo();
      logger.pop();
    }
    foo();
}

Для дальнейшей очистки стеки должны быть скрыты внутри класса DynamicBinding, операции с ручным .push()/. pop() могут быть скрыты за защитой scoped и будут проблемы с многопотоковой обработкой, которые необходимо будет выполнить забота о. Но базовая концепция может работать и дает большую гибкость, чем простая одноэлементная или глобальная переменная.