boost.log std::异常格式化程序找不到运算符<< 自己的命名空间中的重载
boost.log std::exception formatter unable to find operator<< overload in own namespace
我为boost.log创建了一个简单的格式化程序,如本例中std::exception
所示。现在,如果我想使用重载运算符,它是在我自己的命名空间中定义的,那么日志无法找到重载。
部分代码:
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
但如果我将重载移到std命名空间中(Stroustrup请不要射杀我,这只是为了测试(,格式化程序会找到它。
错误消息在formatting_ostream.hpp(boost 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:prgboost1.59.0includeboostlogutilityformatting_ostream.hpp
我的意图是,我有一个自己的异常类(在命名空间My_space中定义(,它继承自std::exception,所以我可以抛出自己的异常,但捕获std::异常。
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命名空间的情况下实现这一点?
您的问题中有两个问题,我将在下面分别解决。
1.名称查找
在C++中,非限定函数调用(如流表达式中的operator<<
(涉及非限定名称查找,这基本上会生成一组可能正在调用的候选函数。然后根据过载解决规则从该集合中选择实际函数。为了完成调用,必须(a(目标函数在候选集合中,(b(考虑到函数的调用方式(提供的参数数量及其类型、显式模板参数等(,该函数相对于集合中的其他函数不含糊。
简单地说,operator<<
查找分三个阶段执行。首先,编译器在右手操作数类中查找成员operator<<
。Boost.Log流中定义的运算符就是这样找到的。接下来,从内部名称空间开始向外移动,在包含函数调用的名称空间中查找一个独立的(非成员(运算符。这里还考虑使用using
指令和声明导入的名称。一旦找到任何operator<<
,该查找就结束。请注意,当您从代码中调用运算符时,以及当您使用log::add_value
时,从Boost.Log调用运算符时所考虑的命名空间是不同的。在第一种情况下,您将my_space
导入到当前的命名空间中,这样就可以找到您的运算符。在后一种情况下,Boost.Log不会导入您的命名空间,因此找不到您的运算符。
最后,编译器执行参数相关查找(ADL(,以收集您可能正在调用的其他函数。基本上,它在相关的名称空间中查找运算符,这些名称空间包括:
- 声明函数调用中每个参数的类型的命名空间。这意味着在这两种情况下都考虑名称空间
std
,因为std::exception
是在那里声明的。当使用Boost.Log时,也会考虑其声明流类型的内部命名空间(它包含一些运算符,但没有一个接受std::exception
( - 如果函数是一个模板,那么它的模板参数类型的名称空间也会被类似地考虑
- 如果函数参数类型或函数模板参数类型本身就是模板,则同样会考虑这些模板参数的命名空间。这是递归完成的
ADL在名称空间std
中找到operator<<
,但它们都不接受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;
}
如果愿意,还可以为my_manip
提供一个通用的operator<<
。
2.添加Boost.Log属性
如果你的初衷是将异常附加到日志记录中,那么恐怕你做得不对。add_value
操纵器不会确定您提供的值的运行时类型,这意味着它会保存std::exception
的副本,从而丢失派生类提供的任何诊断信息(包括what()
消息(。这就是所谓的对象切片。
你可以走多种途径来实现你想要的。首先,您可以在捕获异常的地方格式化错误消息。
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也没有为exception_ptr
提供operator<<
,因此您必须为此实现自定义格式化程序。
- 没有为自己的结构调用列表推回方法
- 在他自己的方法中,有可能将一个对象取消引用到另一个对象吗
- 在c++中为我自己的基于指针的数组分配内存的正确方法
- C++从对象自己的类中删除对象
- 使用 std::optional,而不是自己的结构
- 子轴围绕父轴而不是他自己的轴旋转
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- C++ 如何为自己的迭代器类从迭代器转换为const_iterator?
- 重载 + 自己的类和 std::string 的运算符
- 类无法访问自己的私有静态 constexpr 方法 - Clang bug?
- 是否可以在不填充自己的参数的情况下将模板函数作为参数传递?
- 如何访问模板参数自己的模板参数?
- 将矩阵乘以我自己的输入的向量
- 您应该在什么时候创建自己的异常类型
- 派生类是从基类继承 v 指针并仅使用它,还是也有自己的 v 指针?
- string1 == string2 和你自己的 for 循环比较有什么区别?
- 如何正确包含我自己的标头?
- 自己的自定义向量类. 内存重新分配
- 如何使用我构建的库,而不会从源代码出错,但不为我自己的项目编译?
- Can运算符<<用于设计我们自己的输出显示方式