如何在C++中嵌套词法作用域可访问的作用域中声明静态信息?
How to declare static information in scopes accessible to nested lexical scopes in 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__
),但我想要一个更通用的解决方案。以下是我能想到的限制,使事情变得更容易:
- 合理地限定,但用户不应必须提供当前级别/代码可以移动到不同的作用域而不被更改。也许 16 个级别就足够了(这给了我们 orgname::app::module::subsystem::subsubsystem::d etail::impl::d etail::util 还有一些空间......
- 一个范围(在单个翻译单元中)的下一级范围的数量可以有限制,但应远大于 1 的值。也许 256 是合理的,但我相信有人会有一个反例。
- 理想情况下,同一宏可以用于任何上下文。
我考虑了以下方法:
using __parent_context__ = __log_context__; struct __log_context__ ...
希望
__parent_context__
获取外部上下文,但我收到编译器错误,指示类型名称必须明确引用同一范围内的单个类型。此限制仅适用于在类的主体中使用时,否则这将适用于函数和命名空间。跟踪适用于范围的结构,例如
boost::mpl::vector
教程中的示例让我相信我会遇到与 1 相同的问题,因为推送到后的向量需要被赋予一个不同的名称,该名称需要在嵌套作用域中专门引用。
使用预处理器计数器生成适用的外部作用域的名称。
这在上面的简单用法示例中有效,但如果命名空间中存在不连续的声明或相应类之外的方法定义,则会失败。
如何在嵌套作用域中访问此信息?
好的,我找到了一个解决方案。
诀窍是,即使我们稍后在同一作用域中定义var
,外部作用域中可见var
的decltype(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
如愿以偿。
有条件地继承问题示例中提到的外部作用域将保留为练习。
编写示例需要一些时间,但我会分享我如何处理这个问题。
- 您的
LOG_CONTEXT
可以在任何地方调用,因此如果我们创建多个静态对象,则它们的构造顺序是未知的。 - 您的上下文可以按行号排序,可通过
__LINE__
LOG_CONTEXT
可以创建LoggingContext
结构的静态对象,这些对象在创建时自行注册到本地容器。(本地我的意思是编译对象中唯一,可以通过匿名命名空间实现)LOG_*
应采用其当前行,并从本地寄存器获取最新的 LoggingContext。(如果需要,最后几个)- 我认为所有这些都可以通过
constexpr
语义来实现(但这是一个相当大的挑战)
未解决的问题:
- 函数中的静态对象(在第一次调用中创建)
- 嵌套上下文(也许比较
__FUNCTION__
会起作用)?
附言。我会尝试在周末实现它
- 未在作用域中声明unordered_map
- 未在此作用域OpenCV3.4中声明cvSaveImage
- 未在此作用域中声明的函数和变量 (C++)
- 函数未在作用域中声明 / 如何结合使用 header.h、header.cpp 和 main.cpp?
- 重载时未在 C++ 的作用域错误中声明
- 在 C++ 中使用 "transform" 会给出一个错误,指出这未在作用域中声明
- Tictactoe 游戏变量未在作用域中声明
- 当变量在多个函数作用域中使用时,我应该在类 private 中声明该变量吗?
- 如何在C++中嵌套词法作用域可访问的作用域中声明静态信息?
- gets()未在作用域中声明
- 在C++14中,在哪个作用域中声明了重新声明枚举的非范围枚举器
- C++11 外部作用域变量声明为 auto
- 未在此作用域中声明类 (c++)
- 如何声明在函数作用域中定义的结构
- 未在此作用域中声明函数,即使存在头文件也是如此
- 变量声明中结构的作用域解析是什么意思
- 在C++中,在给定的相同作用域内声明相同的变量名
- 我可以使用相同的名称为周围作用域中的类型声明成员类型别名吗
- 类未在作用域中声明,但已声明该类
- 使用新的外部函数作用域声明和初始化变量