c++ - boost.log std::exception格式化程序无法在自己的命名空间中找到运算符<<重载

标签 c++ exception operator-overloading boost-log

我为boost.log创建了一个简单的格式化程序,如std::exceptionthis示例所示。现在,如果我想使用在我自己的名称空间中定义的重载运算符,则日志无法找到重载。

一些代码:

namespace my_space {
template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, std::exception const& e) {
    // some printout stuff here
    strm << e.what();
    return strm;
}
} // namespace my_space


但是如果我移动(Stroustrup,请不要向我开枪,那只是为了测试),重载到std名称空间中将由格式化程序找到。

错误消息在formatting_ostream.hpp中(提升1.59.0行782)

template< typename StreamT, typename T >
inline typename boost::log::aux::enable_if_formatting_ostream< StreamT, StreamT& >::type
operator<< (StreamT& strm, T const& value)
{...}


使用Visual Studio 2013时,消息显示为:

Error   818 error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'const std::exception' (or there is no acceptable conversion) d:\prg\boost\1.59.0\include\boost\log\utility\formatting_ostream.hpp


我的意图是,我有一个自己的异常类(在名称空间my_space中定义),该类继承自std :: exception,因此我可以抛出自己的异常类,但可以捕获std :: exception。

using namespace my_space;
try {
    // throw(std::runtime_error("something happend."));
    throw(my_exception(0x1, "something happend."));
}
catch (std::exception& e) {
    std::cerr << e << std::endl;  // works just fine
    MY_LOG_ERROR(slg) << log::add_value("Exception", e); // compile error
}


如何在不使用我自己的函数/重载污染std名称空间或创建双重catch块的情况下实现这一目标?

最佳答案

您的问题中有两个问题,下面我将分别解决。

1.名称查找。

在C ++中,不合格的函数调用(例如流表达式中的operator<<)涉及unqualified name lookup,基本上会产生一组可能正在调用的候选函数。然后根据overload resolution rules从该设置中选择实际功能。为了完成调用,考虑到调用函数的方式(所提供参数的数量),(a)预期功能在候选集中,并且(b)对于集合中的其他功能而言,它是不模糊的,这一点很重要。及其类型,显式模板参数等)。在您的情况下(a)无法满足。

简而言之,operator<<查找分三个阶段执行。首先,编译器在右侧操作数类中查找成员operator<<。通过这种方式可以找到Boost.Log流中定义的运算符。接下来,在包含函数调用的名称空间中寻找一个独立的(非成员)运算符,从内部名称空间开始向外移动。在此也考虑使用using指令和声明导入的名称。一旦找到任何operator<<,此查找就会结束。请注意,当您从代码中调用运算符时以及使用log::add_value时从Boost.Log调用运算符时,所考虑的命名空间是不同的。在第一种情况下,您将my_space导入当前的名称空间,因此可以找到您的运算符。在后一种情况下,Boost.Log不会导入您的名称空间,因此找不到您的运算符。

最后,编译器执行依赖于参数的查找(ADL),以收集您可能正在调用的其他函数。基本上,它在关联的名称空间中寻找运算符,该名称空间包括:


声明函数调用中每个参数类型的名称空间。这意味着在两种情况下都考虑使用名称空间std,因为在那里声明了std::exception。使用Boost.Log时,还将考虑其内部声明了流类型的名称空间(它包含一些运算符,但没有一个运算符接受std::exception)。
如果函数是模板,则类似地考虑其模板参数类型的名称空间。
如果函数参数类型或函数模板参数类型是模板本身,则同样考虑这些模板参数的名称空间。这是递归完成的。


ADL在名称空间operator<<中找到std,但是它们都不接受std::exception。此处的运算符查找的最终结果是,仅在my_space指令中找到了您的运算符,原因是您的using指令,当从程序的另一点(例如Boost.Log)调用运算符时,这无济于事码。

实施运营商时,最佳实践是依靠ADL查找那些运营商。这意味着必须将支持某种类型的运算符放在声明该类型的相同名称空间中。对于std::exception,这是名称空间std。从技术上讲,向名称空间std添加内容会导致未定义的行为(根据[namespace.std] / 1),因此最好的解决方案是在名称空间中定义自己的流操纵器,并使用它将异常输出到流中:

namespace my_space {

template< typename T >
struct my_manip
{
  T const& value;
};
template< typename T >
my_manip< T > to_stream(T const& value) {
  my_manip< T > m = { value };
  return m;
}

template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (
  std::basic_ostream< CharT, TraitsT >& strm,
  my_manip< std::exception > const& e)
{
  // some printout stuff here
  strm << e.value.what();
  return strm;
}

} // namespace my_space

try {
  // ...
}
catch (std::exception& e) {
  std::cerr << my_space::to_stream(e) << std::endl;
}


如果愿意,还可以为operator<<提供通用的my_manip

2.添加Boost.Log属性。

如果您的初衷是在日志记录中附加例外,那么恐怕您的做法不正确。 add_value操纵器无法确定您提供的值的运行时类型,这意味着它将保存std::exception的副本,从而丢失派生类提供的任何诊断信息(包括what()消息)。这称为object slicing

您可以走多条路线来实现您想要的。首先,您可以在捕获异常的地方格式化错误消息。

catch (std::exception& e) {
  MY_LOG_ERROR(slg) << e.what();
}


您将不能将其用作接收器或格式化程序中的属性值,但这对您而言可能就足够了。当然,您也可以使用自定义操纵器:

catch (std::exception& e) {
  MY_LOG_ERROR(slg) << my_space::to_stream(e);
}


如果确实需要将其用作属性值,则必须选择所需的信息类型。例如,如果您只需要错误消息,则可以附加它而不是异常:

catch (std::exception& e) {
  MY_LOG_ERROR(slg) << log::add_value("ErrorMessage", std::string(e.what()));
}


如果异常本身是您所需要的,则需要C ++ 11 exception_ptr

catch (std::exception& e) {
  MY_LOG_ERROR(slg) << log::add_value("Exception", std::current_exception());
}


在C ++ 03中,可以使用Boost.Exception或实现用于确定异常动态类型的自定义机制。请注意,标准库或Boost.Exception也没有为operator<<提供exception_ptr,因此您必须为此实现自定义格式化程序。

关于c++ - boost.log std::exception格式化程序无法在自己的命名空间中找到运算符<<重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32524347/

相关文章:

java - 找不到资源java

c++ - 比较有理数

c++ - 在枚举上重载 << 运算符会导致运行时错误

c++ - 这是一个引用到非常量绑定(bind)到临时的实例吗?

带网页的 C++ 输出

c# - 是否可以抛出 MessageQueueException?

spring - 使用 mockmvc 检查异常场景中的空内容主体

c++ - 在 OpenCL 中获得最佳的本地/全局工作组规模?

c++ - 如果我重载了 + 和 = 运算符,我是否必须重载 +=?

c++ - 对字符串使用小于比较运算符