boost.log std :: exception formatter无法找到运算符<<重载在自己的命名空间

问题描述:

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

I have created a simple formatter for boost.log like shown in this example for std::exception. Now if i want to use the overloaded operator, which is defined in my own namespace, log is unable to find the overload.

一些代码:

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

但是如果我移动

错误消息是在formatting_ostream.hpp(boost) 1.59.0 line 782)

The error message is in formatting_ostream.hpp (boost 1.59.0 line 782)

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

Studio 2013的消息说:

With Visual Studio 2013 the message say's:

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 intention is, i have an own exception class (defined in namespace my_space) which inherits from std::exception, so i can throw my own but catch a 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块。

How to achieve this without polluting the std namespace with my own functions/overloads or creating double catch-blocks?

在你的问题有两个问题,

There are two problems in your question that I'll address separately below.

在C ++中,未限定的函数调用,如流表达式中的运算符< 涉及无限制名称查找,这基本上产生一组候选功能,您可以打电话。之后,根据重载解析规则选择实际功能。对于完成的调用,考虑调用函数的方式(提供的参数的数量),(a)期望的函数在候选集合中和(b)相对于集合中的其他函数不是不明确的,以及它们的类型,显式模板参数等)。在你的情况下(a)不能满足。

In C++, unqualified function calls, such as the operator<< in your streaming expression, involve the unqualified name lookup, which basically produces a set of candidate functions that you may be calling. Out of this set the actual function is then selected according to the overload resolution rules. For the call to complete it is essential that (a) the intended function is in the candidate set and (b) that it is not ambiguous with respect to other functions in the set, considering the way the function is called (number of provided arguments and their types, explicit template parameters, etc.) In your case (a) is not fulfilled.

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

Put simply and closer to your code, the operator<< lookup is performed in three stages. First, the compiler looks for a member operator<< in the right hand operand class. The operators defined in Boost.Log streams are found this way. Next, a free standing (non-member) operator is looked for in the namespaces enclosing the function call, starting from the inner namespaces and moving outwards. Names imported with using directives and declarations are also considered here. This lookup ends as soon as any operator<< is found. Note that the namespaces considered are different when you invoke the operator from your code and when the operator is invoked from Boost.Log, when you use log::add_value. In the first case you import my_space into your current namespace, so your operator is found. In the latter case Boost.Log does not import your namespace, so your operator is not found.

最后,编译器执行参数相关的查找( ADL )以收集您可能正在呼叫的其他功能。基本上,它在由以下组成的相关命名空间中查找运算符:

Lastly, the compiler performs argument-dependent lookup (ADL) to collect additional function you might be calling. Basically, it looks for the operator in the associated namespaces which are comprised of:


  • 函数调用中每个参数类型的命名空间被声明。这意味着在两种情况下都会考虑命名空间 std ,因为 std :: exception 被声明。当使用Boost.Log时,其中声明流类型的内部命名空间也被考虑(它包含几个运算符,但是它们都不接受 std :: exception )。 / li>
  • 如果函数是一个模板,它的模板参数类型的命名空间也同样被考虑。

  • 如果函数参数类型或函数模板参数类型模板本身,那些模板参数的命名空间也被类似地考虑。这是递归完成的。

  • The namespace where the type of every argument in the function call is declared. That means that namespace std is considered in both cases because std::exception is declared there. When Boost.Log is used, its internal namespace where the stream type is declared is also considered (it contains a few operators but none of them accepts std::exception).
  • If the function is a template, namespaces of its template argument types are also similarly considered.
  • If the function argument types or function template argument types are templates themselves, those template arguments' namespaces are also similarly considered. This is done recursively.

ADL在命名空间中找到 operator<< std ,但是它们都不接受 std :: exception 。这里的操作符查找的净效果是, my_space 中的操作符只是因为您的使用 -directive,当从程序的另一点调用操作符(如Boost.Log代码)时,这不会有帮助。

The ADL finds operator<< in namespace std but none of them accepts std::exception. The net effect of the operator lookup here is that your operator in my_space is only found because of your using-directive, which doesn't help when the operator is called from another point of the program, such as Boost.Log code.

当实现运算符时,最佳实践是依靠ADL找到这些运算符。这意味着支持类型的运算符必须放在声明该类型的同一命名空间中。在 std :: exception 的情况下,这是命名空间 std 。从技术上来说,向命名空间 std 添加内容会导致未定义的行为(如[namespace.std] / 1),所以最好的解决方案是在你的命名空间并使用它将流输出到流中:

When implementing operators, the best practice is to rely on ADL finding those operators. This means that the operators supporting a type must be placed in the same namespace where that type is declared. In case of std::exception, this is namespace std. Technically, adding stuff to namespace std results in undefined behavior (as per [namespace.std]/1), so the best solution would be to define your own stream manipulator in your namespace and use it to output exceptions into streams:

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

您还可以提供一个通用运算符< c>

You can also provide a generalized operator<< for my_manip, if you like.

如果您的初衷是将异常附加到日志记录,那么恐怕您不能以正确的方式执行。 add_value 操纵器不会确定您提供的值的运行时类型,这意味着它会保存 std :: exception ,从而丢失由派生类(包括 what()消息)提供的任何诊断信息。这称为对象切片

If your original intention is to attach exceptions to a log record then I'm afraid you're not doing it the right way. The add_value manipulator does not determine the runtime type of the value you provide, which means it saves a copy of std::exception, thus losing any diagnostic information that is provided by the derived class (including the what() message). This is known as object slicing.

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

You can go multiple routes to implement what you want. First, you can format the error message in the place where you caught the exception.

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

您不能将其用作汇的属性值格式化,但这可能就够了。您还可以使用您的自定义操纵器,当然:

You won't be able to use it as an attribute value in sinks or formatters, but this might be enough for you. You can also use your custom manipulator, of course:

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

如果你确实需要它作为属性值,你想要的那种信息。例如,如果您需要的是错误消息,您可以附加它而不是异常:

If you do need it as an attribute value, you will have to choose what kind of information you want. For example, if all you need is the error message, you could attach it instead of the exception:

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 提供运算符<< 必须为此实现自定义格式化程序。

In C++03 you can use Boost.Exception or implement your custom mechanism of determining the dynamic type of the exception. Note that the standard library or Boost.Exception also don't provide operator<< for exception_ptr, so you'll have to implement custom formatters for that.