Могу ли я переписать макрос ведения журнала с помощью потоковых операторов, чтобы использовать шаблонную функцию C ++? - PullRequest
7 голосов
/ 12 марта 2012

Наш проект использует макрос, чтобы сделать регистрацию легкой и простой в виде однострочных операторов, например:

DEBUG_LOG(TRACE_LOG_LEVEL, "The X value = " << x << ", pointer = " << *x);

Макрос переводит второй параметр в аргументы stringstream и отправляет его обычному регистратору C ++. Это прекрасно работает на практике, поскольку делает многопараметрические операторы регистрации очень лаконичными. Тем не менее, Скотт Мейерс сказал в Effective C ++ 3rd Edition : «Вы можете получить всю эффективность макроса плюс все предсказуемое поведение и безопасность типов регулярной функции, используя шаблон для встроенной функции» (Пункт 2). Я знаю, что в C ++ есть много проблем с использованием макросов, связанных с предсказуемым поведением, поэтому я пытаюсь устранить как можно больше макросов в нашей кодовой базе.

Мой макрос регистрации определен как:

#define DEBUG_LOG(aLogLevel, aWhat) {  \
if (isEnabled(aLogLevel)) {            \
  std::stringstream outStr;            \
  outStr<< __FILE__ << "(" << __LINE__ << ") [" << getpid() << "] : " << aWhat;    \
  logger::log(aLogLevel, outStr.str());    \
}

Я несколько раз пытался переписать это во что-то, что не использует макросы, в том числе:

inline void DEBUG_LOG(LogLevel aLogLevel, const std::stringstream& aWhat) {
    ...
}

И ...

template<typename WhatT> inline void DEBUG_LOG(LogLevel aLogLevel, WhatT aWhat) {
    ...  }

Безрезультатно (ни один из двух приведенных выше вариантов не скомпилируется с нашим кодом регистрации в 1-м примере). Есть еще идеи? Можно ли это сделать? Или лучше оставить это как макрос?

Ответы [ 3 ]

5 голосов
/ 12 марта 2012

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

Однако вы можете переместить логику регистрации в отдельную функцию (или объект) и предоставить только информацию о месте вызова через макрос. Вам даже не нужна функция шаблона для этого.

#define DEBUG_LOG(Level, What) \
  isEnabled(Level) && scoped_logger(Level, __FILE__, __LINE__).stream() << What

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

Теперь scoped_logger будет объектом RAII, который будет фактически регистрировать, что он получает, когда он уничтожен, то есть в деструкторе.

struct scoped_logger
{
  scoped_logger(LogLevel level, char const* file, unsigned line)
    : _level(level)
  { _ss << file << "(" << line << ") [" << getpid() << "] : "; }

  std::stringstream& stream(){ return _ss; }
  ~scoped_logger(){ logger::log(_level, _ss.str()); }
private:
  std::stringstream _ss;
  LogLevel _level;
};

Разоблачение базового объекта std::stringstream избавляет нас от необходимости писать наши собственные operator<< перегрузки (что было бы глупо). Необходимость действительно выставить это через функцию важна; если объект scoped_logger является временным (значение r), то же самое относится и к элементу std::stringstream, и будут найдены только перегрузки элемента operator<<, если мы каким-либо образом не преобразуем его в lvalue (ссылка). Вы можете прочитать больше об этой проблеме здесь (обратите внимание, что эта проблема была исправлена ​​в C ++ 11 с помощью потоковых вставок rvalue). Это «преобразование» выполняется путем вызова функции-члена, которая просто возвращает нормальную ссылку на поток.

Небольшой живой пример на Ideone.

5 голосов
/ 12 марта 2012

Нет, этот точный макрос нельзя переписать как шаблон, так как вы используете операторы (<<) в макросе, которые нельзя передать в качестве аргумента шаблона или аргумента функции. </p>

У нас была та же проблема, и мы решили ее с помощью подхода, основанного на классах, с использованием синтаксиса, подобного

DEBUG_LOG(TRACE_LOG_LEVEL) << "The X value = " << x << ", pointer = " << *x << logger::flush;

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

3 голосов
/ 12 марта 2012

Проблема преобразования этого конкретного макроса в функцию заключается в том, что такие вещи, как "The X value = " << x, не являются допустимыми выражениями.

Оператор << является левоассоциативным, что означает что-то в форме A << B << Cрассматривается как (A << B) << C.Перегруженные операторы вставки для iostreams всегда возвращают ссылку на один и тот же поток, чтобы вы могли выполнять больше операций вставки в одном и том же операторе.То есть, если A является std::stringstream, поскольку A << B возвращает A, (A << B) << C; имеет тот же эффект, что и A << B; A << C;.

Теперь вы можете передать B << C в макроспросто хорошо.Макрос просто обрабатывает его как набор токенов и не беспокоится о том, что они значат, пока не будет выполнено все подстановка.В этот момент может сработать лево-ассоциативное правило. Но для любого аргумента функции, даже если он встроенный и шаблонный, компилятору необходимо выяснить, каков тип аргумента и как найти его значение.Если B << C недопустимо (поскольку B не является ни потоком, ни целым числом), ошибка компилятора.Даже если B << C является действительным, поскольку параметры функции всегда оцениваются перед чем-либо в вызываемой функции, вы в конечном итоге получите поведение A << (B << C), которое здесь не то, что вам нужно.

Если вы 'Желая изменить все виды использования макроса (скажем, использовать запятые вместо << токенов или что-то вроде предложения @ svenihoney), есть способы сделать что-то.Если нет, то этот макрос просто нельзя рассматривать как функцию.

Я бы сказал, что в этом макросе нет никакого вреда, поскольку все программисты, которые должны его использовать, будут понимать, почему в строкеначиная с DEBUG_LOG, они могут увидеть ошибки компилятора, относящиеся к std::stringstream и / или logger::log.

Если вы храните макрос, ознакомьтесь с ответами на часто задаваемые вопросы по C ++ 39.4 и 39,5 за хитрости, чтобы избежать нескольких неприятных способов, такие макросы могут удивить вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...