如何在C ++中的嵌套词法范围可访问的范围中声明静态信息? [英] How to declare static information in scopes accessible to nested lexical scopes in C++?

查看:86
本文介绍了如何在C ++中的嵌套词法范围可访问的范围中声明静态信息?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想声明作用域的标识符,这些标识符将用于自动填充最内层作用域内任何日志记录语句的字段.它们通常(但并非总是)(例如,lambdas,由{}引入的块)与封闭块的名称"匹配.

用法看起来像这样:

 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中相同的问题,因为推入后的向量需要被赋予一个唯一的名称,该名称需要在嵌套作用域中专门引用. /p>

  3. 使用预处理器计数器生成适用的外部作用域的名称.

    这将在上面的简单用法示例中起作用,但是在对应类之外的名称空间或方法定义中存在不连续的声明时将失败.

如何在嵌套作用域中访问此信息?

解决方案

好的,我找到了解决方法.

诀窍在于,即使我们稍后在同一作用域中定义var,在外部作用域中可见的vardecltype(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

根据需要.

如问题示例所述,有条件地继承外部作用域作为练习.

I want to declare identifiers for scopes which will be used to automatically populate a field of any logging statements within the innermost scope. They will usually, but not always (e.g. lambdas, blocks introduced with {}), match the "name" of the enclosing block.

Usage would look something like this:

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" );
}

with the result being something like:

[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

We can get the inner-most scope by defining the LOG_CONTEXT macro such that it declares a struct. Then in the LOG_* macros we call a static method on it to retrieve the name. We pass the whole thing to a callable object, e.g.:

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" );

Where I'm stuck is building the stack of contexts to use when defining an inner-most __log_context__. We may use a differently-named structure and macro convention to add 1 or 2 levels (e.g. LOG_MODULE can define a __log_module__), but I want a more general solution. Here are the restrictions I can think of to make things easier:

  1. Scope nesting level may be reasonably bounded, but the user should not have to provide the current level/code may be moved to a different scope without being changed. Maybe 16 levels is enough (that gives us orgname::app::module::subsystem::subsubsystem::detail::impl::detail::util with some room to spare...)
  2. Number of next-level scopes within a scope (in a single translation unit) may be bounded, but should be much larger than the value for 1. Maybe 256 is reasonable, but I'm sure someone will have a counterexample.
  3. Ideally the same macro could be used for any context.

I have considered the following approaches:

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

    hoping that __parent_context__ picks up the outer context, but I was getting compiler errors indicating that a type name must refer unambiguously to a single type in the same scope. This restriction applies only when used in the body of a class, this would otherwise work for functions and namespaces.

  2. Tracking the structures applicable to a scope in something like boost::mpl::vector

    The examples in the tutorial lead me to believe I would run into the same issue as in 1, since the vector after being pushed to needs to be given a distinct name which would need to be referred to specifically in nested scopes.

  3. Generating the name of the applicable outer scope with a preprocessor counter.

    This would work in my simple usage example above, but would fail in the presence of discontinuous declarations in a namespace or method definitions outside of the corresponding class.

How can this information be accessed in the nested scopes?

解决方案

Okay, I found a solution.

The trick is that a decltype(var) for var visible in the outer scope will resolve to the type of that outer scope var even if we define var later in the same scope. This allows us to shadow the outer type but still access it, via an otherwise unused variable of the outer type, while allowing us to define a variable of the same name to be accessed in inner scopes.

Our general construction looks like

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__;

The only other detail is that we need a terminating condition when iterating up the context chain, so we use void* as a sentinel value and specialize for it in the helper classes used for constructing the output string.

C++11 is required for decltype and to allow local classes to be passed to template parameters.

#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" );

which with an approximation of the code in my initial post

#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" );
}

yields

[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

as desired.

Conditionally inheriting an outer scope as mentioned in the question example is left as an exercise.

这篇关于如何在C ++中的嵌套词法范围可访问的范围中声明静态信息?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆