C ++ отключить цепной вызов без переноса в директиву

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

log::error << "some" << " log " << "message";

Я реализовал это так:

  1. log::error#operator<< возвращает временный Sink объект.

  2. Sink#operator<< возвращается *this и определяет конструктор перемещения.

  3. Полное сообщение может быть использовано в Sinkдеструктор, который вызывается в конце цепочки вызовов.

Придуманная реализация:

#include <iostream>
#include <string>

struct Sink {

Sink (std::string const& msg) : m_message(msg) {}

// no copying
Sink (Sink const& orig) = delete;

// move constructor
Sink (Sink && orig) : m_message(std::move(orig.m_message)) {};

// use the complete string in the destructor
~Sink() { std::cerr << m_message << std::endl;}

Sink operator<< (std::string const& msg) {
m_message.append(msg);
return std::move(*this);
}

std::string m_message;
};

struct Level {
Sink operator<< (std::string const& msg) { return Sink(msg); }
};

int main() {
Level log;

log << "this" << " is " << "a " << "test";
}

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

void log (std::string) {
#ifdef LOGGING_ENABLED
// log message
#endif
}

Компилятор затем оптимизирует и удаляет пустой вызов функции. Но я не знаю, как бы я это сделал с API, который я пытаюсь достичь. Я знаю, что это возможно, потому что GLog делает это как-то.

Использование таких директив побеждает цель иметь хороший API.

#ifdef LOGGING_ENABLED
log << "this" << " is " << "a " << "test";
#endif

Что такое чистый способ отключения этих типов связанных вызовов?
Любая помощь приветствуется.

2

Решение

Вы должны реализовать другого Sink который ничего не делает при регистрации. Глог называет это нулевым потоком:

// A class for which we define operator<<, which does nothing.
class GOOGLE_GLOG_DLL_DECL NullStream : public LogMessage::LogStream {
public:
// Initialize the LogStream so the messages can be written somewhere
// (they'll never be actually displayed). This will be needed if a
// NullStream& is implicitly converted to LogStream&, in which case
// the overloaded NullStream::operator<< will not be invoked.
NullStream() : LogMessage::LogStream(message_buffer_, 1, 0) { }
NullStream(const char* /*file*/, int /*line*/,
const CheckOpString& /*result*/) :
LogMessage::LogStream(message_buffer_, 1, 0) { }
NullStream &stream() { return *this; }
private:
// A very short buffer for messages (which we discard anyway). This
// will be needed if NullStream& converted to LogStream& (e.g. as a
// result of a conditional expression).
char message_buffer_[2];
};

// Do nothing. This operator is inline, allowing the message to be
// compiled away. The message will not be compiled away if we do
// something like (flag ? LOG(INFO) : LOG(ERROR)) << message; when
// SKIP_LOG=WARNING. In those cases, NullStream will be implicitly
// converted to LogStream and the message will be computed and then
// quietly discarded.
template<class T>
inline NullStream& operator<<(NullStream &str, const T &) { return str; }

В вашем случае простая реализация будет выглядеть

#ifdef LOGGING_ENABLED
/* your sink */
#else
struct Sink {
Sink (std::string)  {}
Sink (Sink const& orig) {};
};
template <typename T> Sink operator<<(Sink s, T) { return s; }
#endif

Это очень просто и может быть оптимизировано вне компилятора.

4

Другие решения

Это не самая красивая, но вы могли бы сделать что-то вроде этого:

#ifdef LOGGING_ENABLED
#define LOG(message) message
#else
#define LOG(message)
#endifLOG(log << "this" << "is" << "a" << "test");

Вы могли бы немного упростить это, сделав это

#ifdef LOGGING_ENABLED
#define LOG(message) log << message
#else
#define LOG(message)
#endifLOG("this" << "is" << "a" << "test");
1

Есть одна проблема с потоковым подходом, даже с нулевым потоком: в C ++ нет ленивых вычислений.

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

Единственный способ избежать этой оценки — использовать макрос:

#define LOG(Message_) \
do (LogManager::activated()) {
logger << Message_;
} while(0);

Конечно, я хотел бы отметить, что если вы используете макрос, это хорошая чертова возможность __func__, __FILE__ а также __LINE__,

1
По вопросам рекламы [email protected]