Boost.log Std:: Исключитель форматирования не удалось найти оператор << перегрузки в собственном пространстве имен
Я создал простой форматер для 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
Но если я перееду (Stroustrup, пожалуйста, не стреляйте в меня, это было только для тестирования), перегрузка в пространство имен std будет найдена форматером.
Сообщение об ошибке находится в 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:\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 ответ
В вашем вопросе есть две проблемы, которые я рассмотрю отдельно ниже.
1. Название поиска.
В C++ вызовы неквалифицированных функций, такие как operator<<
в своем выражении потоковой передачи включите поиск без определения имени, который в основном производит набор функций-кандидатов, которые вы, возможно, вызываете. Из этого набора фактическая функция затем выбирается в соответствии с правилами разрешения перегрузки. Для завершения вызова важно, чтобы (а) предполагаемая функция была в наборе кандидатов и (б) чтобы она не была неоднозначной по отношению к другим функциям в наборе, учитывая способ вызова функции (количество предоставленных аргументов и их типы, явные параметры шаблона и т. д.) В вашем случае (а) не выполняется.
Проще говоря и ближе к вашему коду, operator<<
поиск выполняется в три этапа. Во-первых, компилятор ищет член operator<<
в правом классе операндов. Операторы, определенные в потоках Boost.Log, находятся таким образом. Затем ищется автономный оператор (не являющийся членом) в пространствах имен, охватывающих вызов функции, начиная с внутренних пространств имен и двигаясь наружу. Имена, импортированные с using
директивы и декларации также рассматриваются здесь. Этот поиск заканчивается, как только operator<<
найден. Обратите внимание, что рассматриваемые пространства имен различаются, когда вы вызываете оператор из своего кода и когда оператор вызывается из Boost.Log, когда вы используете log::add_value
, В первом случае вы импортируете my_space
в ваше текущее пространство имен, так что ваш оператор найден. В последнем случае Boost.Log не импортирует ваше пространство имен, поэтому ваш оператор не найден.
Наконец, компилятор выполняет зависимый от аргумента поиск ( ADL) для сбора дополнительной функции, которую вы можете вызывать. По сути, он ищет оператора в связанных пространствах имен, которые состоят из:
- Пространство имен, в котором объявлен тип каждого аргумента в вызове функции. Это означает, что пространство имен
std
считается в обоих случаях, потому чтоstd::exception
там объявлено При использовании Boost.Log также учитывается его внутреннее пространство имен, в котором объявлен тип потока (он содержит несколько операторов, но ни один из них не принимаетstd::exception
). - Если функция является шаблоном, пространства имен ее типов аргументов шаблона также рассматриваются аналогично.
- Если типы аргументов функции или типы аргументов шаблона функции являются самими шаблонами, пространства имен этих аргументов шаблона также рассматриваются аналогичным образом. Это делается рекурсивно.
ADL находит operator<<
в пространстве имен std
но никто из них не принимает std::exception
, Чистый эффект поиска оператора здесь заключается в том, что ваш оператор в my_space
найден только из-за вашего using
-directive, который не помогает, когда оператор вызывается из другой точки программы, такой как код 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()
сообщение). Это известно как нарезка объектов.
Вы можете пройти несколько маршрутов, чтобы реализовать то, что вы хотите. Сначала вы можете отформатировать сообщение об ошибке в том месте, где вы поймали исключение.
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
так что вам придется реализовать собственные форматеры для этого.