c ++ 11 — Как объявить статическую информацию в областях, доступных для вложенных лексических областей в C ++?

Я хочу объявить идентификаторы для областей, которые будут использоваться для автоматического заполнения поля любых операторов записи в самой внутренней области видимости. Они обычно, но не всегда (например, лямбды, блоки, введенные с {}), соответствует «имени» вмещающего блока.

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

namespace app {

LOG_CONTEXT( "app" );

class Connector {
LOG_CONTEXT( "Connector" );
void send( const std::string &  msg )
{
LOG_CONTEXT( "send()" );
LOG_TRACE( msg );
}
};

} // namespace app

//                     not inherited
LOG_CONTEXT( "global", false );

void fn()
{
LOG_DEBUG( "in fn" );
}

int main()
{
LOG_CONTEXT( "main()" );
LOG_INFO( "starting app" );
fn();
Connector c;
c.send( "hello world" );
}

в результате получается что-то вроде:

[2018-03-21 10:17:16.146] [info] [main()] starting app
[2018-03-21 10:17:16.146] [debug] [global] in fn
[2018-03-21 10:17:16.146] [trace] [app.Connector.send()] hello world

Мы можем получить самый внутренний объем, определив LOG_CONTEXT макрос такой, что он объявляет структуру. Тогда в LOG_* макросы мы вызываем статический метод для получения имени. Мы передаем все это вызываемому объекту, например:

namespace logging {

spdlog::logger & instance()
{
auto sink =
std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
decltype(sink) sinks[] = {sink};
static spdlog::logger logger(
"console", std::begin( sinks ), std::end( sinks ) );
return logger;
}

// TODO: stack-able context
class log_context
{
public:
log_context( const char *  name )
: name_( name )
{}

const char * name() const
{ return name_; }

private:
const char *  name_;
};

class log_statement
{
public:
log_statement( spdlog::logger &           logger,
spdlog::level::level_enum  level,
const log_context &        context )
: logger_ ( logger  )
, level_  ( level   )
, context_( context )
{}

template<class T, class... U>
void operator()( const T &  t, U&&...  u )
{
std::string  fmt = std::string( "[{}] " ) + t;
logger_.log(
level_,
fmt.c_str(),
context_.name(),
std::forward<U>( u )... );
}

private:
spdlog::logger &           logger_;
spdlog::level::level_enum  level_;
const log_context &        context_;
};

} // namespace logging

#define LOGGER ::logging::instance()

#define CHECK_LEVEL( level_name ) \
LOGGER.should_log( ::spdlog::level::level_name )

#define CHECK_AND_LOG( level_name )      \
if ( !CHECK_LEVEL( level_name ) ) {} \
else                                 \
::logging::log_statement(        \
LOGGER,                      \
::spdlog::level::level_name, \
__log_context__::context() )

#define LOG_TRACE CHECK_AND_LOG( trace )
#define LOG_DEBUG CHECK_AND_LOG( debug )
#define LOG_INFO CHECK_AND_LOG( info )
#define LOG_WARNING CHECK_AND_LOG( warn )
#define LOG_ERROR CHECK_AND_LOG( err )
#define LOG_CRITICAL CHECK_AND_LOG( critical )

#define LOG_CONTEXT( name_ )                        \
struct __log_context__                          \
{                                               \
static ::logging::log_context context()     \
{                                           \
return ::logging::log_context( name_ ); \
}                                           \
}

LOG_CONTEXT( "global" );

То, где я застрял, — это создание стека контекстов для использования при определении самого внутреннего __log_context__, Мы можем использовать структуру с другим именем и соглашение о макросах, чтобы добавить 1 или 2 уровня (например, LOG_MODULE может определить __log_module__), но я хочу более общее решение. Вот ограничения, которые я могу придумать, чтобы сделать вещи проще:

  1. Уровень вложения области может быть разумно ограничен, но пользователь не должен предоставлять текущий уровень / код может быть перемещен в другую область без изменения. Может быть, 16 уровней достаточно (что дает нам orgname :: app :: module :: subsystem :: subsubsystem :: detail :: impl :: detail :: util с некоторым количеством свободного места …)
  2. Количество областей следующего уровня в области (в одной единице перевода) может быть ограничено, но должно быть намного больше, чем значение для 1. Может быть, 256 — разумно, но я уверен, что у кого-то будет контрпример.
  3. В идеале один и тот же макрос можно использовать для любого контекста.

Я рассмотрел следующие подходы:

  1. using __parent_context__ = __log_context__; struct __log_context__ ...

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

  2. Отслеживание структур, применимых к области в чем-то вроде boost::mpl::vector

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

  3. Генерация имени применимого внешнего контекста с помощью счетчика препроцессора.

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

Как получить доступ к этой информации во вложенных областях?

2

Решение

Хорошо, я нашел решение.

Хитрость в том, что decltype(var) за var видимый во внешней области видимости будет соответствовать типу этой внешней области видимости var даже если мы определим var позже в том же объеме. Это позволяет нам скрывать внешний тип, но при этом получать к нему доступ через неиспользуемую в противном случае переменную внешнего типа, в то же время позволяя нам определять переменную с тем же именем для доступа во внутренних областях.

Наша общая конструкция выглядит как

struct __log_context__
{
typedef decltype(__log_context_var__) prev;
static const char * name() { return name_; }
static ::logging::log_context  context()
{
return ::logging::log_context(
name(), chain<__log_context__>::get() );
}
};
static __log_context__ __log_context_var__;

Единственная другая деталь — нам нужно завершающее условие при итерации цепочки контекста, поэтому мы используем void* в качестве значения дозорного и специализироваться на нем в вспомогательных классах, используемых для построения выходной строки.

C ++ 11 требуется для decltype и разрешить передачу локальных классов в параметры шаблона.

#include <spdlog/spdlog.h>

namespace logging {

spdlog::logger & instance()
{
auto sink =
std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
decltype(sink) sinks[] = {sink};
static spdlog::logger logger(
"console", std::begin( sinks ), std::end( sinks ) );
return logger;
}

class log_context
{
public:
log_context( const char *         name,
const std::string &  scope_name )
: name_ ( name       )
, scope_( scope_name )
{}

const char * name() const
{ return name_; }

const char * scope() const
{ return scope_.c_str(); }

private:
const char *  name_;
std::string   scope_;
};

class log_statement
{
public:
log_statement( spdlog::logger &           logger,
spdlog::level::level_enum  level,
const log_context &        context )
: logger_ ( logger  )
, level_  ( level   )
, context_( context )
{}

template<class T, class... U>
void operator()( const T &  t, U&&...  u )
{
std::string  fmt = std::string( "[{}] " ) + t;
logger_.log(
level_,
fmt.c_str(),
context_.scope(),
std::forward<U>( u )... );
}

private:
spdlog::logger &           logger_;
spdlog::level::level_enum  level_;
const log_context &        context_;
};

} // namespace logging

// Helpers for walking up the lexical scope chain.
template<class T, class Prev = typename T::prev>
struct chain
{
static std::string get()
{
return (chain<Prev, typename Prev::prev>::get() + ".")
+ T::name();
}
};

template<class T>
struct chain<T, void*>
{
static std::string get()
{
return T::name();
}
};

#define LOGGER ::logging::instance()

#define CHECK_LEVEL( level_name ) \
LOGGER.should_log( ::spdlog::level::level_name )

#define CHECK_AND_LOG( level_name )      \
if ( !CHECK_LEVEL( level_name ) ) {} \
else                                 \
::logging::log_statement(        \
LOGGER,                      \
::spdlog::level::level_name, \
__log_context__::context() )

#define LOG_TRACE CHECK_AND_LOG( trace )
#define LOG_DEBUG CHECK_AND_LOG( debug )
#define LOG_INFO CHECK_AND_LOG( info )
#define LOG_WARNING CHECK_AND_LOG( warn )
#define LOG_ERROR CHECK_AND_LOG( err )
#define LOG_CRITICAL CHECK_AND_LOG( critical )

#define LOG_CONTEXT_IMPL(prev_type,name_)            \
struct __log_context__                               \
{                                                    \
typedef prev_type prev;                          \
static const char * name() { return name_; }     \
static ::logging::log_context  context()         \
{                                                \
return ::logging::log_context(               \
name(), chain<__log_context__>::get() ); \
}                                                \
};                                                   \
static __log_context__ __log_context_var__

#define LOG_CONTEXT(name_) \
LOG_CONTEXT_IMPL(decltype(__log_context_var__),name_)

#define ROOT_CONTEXT(name_) \
LOG_CONTEXT_IMPL(void*,name_)

// We include the root definition here to ensure that
// __log_context_var__ is always defined for any uses of
// LOG_CONTEXT.
ROOT_CONTEXT( "global" );

который с аппроксимацией кода в моем первоначальном посте

#include <logging.hpp>

namespace app {

LOG_CONTEXT( "app" );

class Connector {
LOG_CONTEXT( "Connector" );

public:
void send( const std::string &  msg )
{
LOG_CONTEXT( "send()" );
LOG_TRACE( msg );
}
};

} // namespace app

void fn()
{
LOG_DEBUG( "in fn" );
}

int main()
{
LOG_CONTEXT( "main()" );
LOGGER.set_level( spdlog::level::trace );
LOG_INFO( "starting app" );
fn();
app::Connector c;
c.send( "hello world" );
}

доходность

[2018-03-22 22:35:06.746] [console] [info] [global.main()] starting app
[2018-03-22 22:35:06.747] [console] [debug] [global] in fn
[2018-03-22 22:35:06.747] [console] [trace] [global.app.Connector.send()] hello world

по желанию.

Условное наследование внешней области, как упомянуто в примере вопроса, оставлено в качестве упражнения.

2

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

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

  • Ваш LOG_CONTEXT может вызываться где угодно, поэтому, если мы создадим несколько статических объектов, порядок их построения неизвестен.
  • Ваши контексты могут быть отсортированы по номеру строки, который доступен с __LINE__
  • LOG_CONTEXT может создавать статические объекты LoggingContext struct, которая самостоятельно регистрируется в создаваемом локальном контейнере. (Под локальным я имею в виду уникальный в объекте компиляции, может быть достигнуто с помощью анонимного пространства имен)
  • LOG_* должен взять их текущую строку и взять самый последний LoggingContext из локального регистра. (Или несколько последних, если нужно)
  • Я думаю, что все это возможно с constexpr сематика (но довольно сложная задача)

Открытые проблемы:

  • Статические объекты в функциях (создаются при первом вызове)
  • Вложенность контекстов (возможно, сравнение __FUNCTION__ должно сработать)?

PS. Я постараюсь реализовать это на выходных

0

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