2015-09-11 2 views
0

Я создал простой форматировщик для boost.log, как показано в примере this для std::exception. Теперь, если я хочу использовать перегруженный оператор, который определен в моем собственном пространстве имен, журнал не может найти перегрузку.boost.log std :: exception formatter не может найти оператора << перегрузка в собственном пространстве имен

Некоторый код:

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 (повышающее 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 
} 

Как добиться этого, не загрязняя патезрасе со своими собственными функциями/перегрузками или созданием двойных уловов-блоков?

ответ

4

В вашем вопросе есть две проблемы, о которых я расскажу отдельно.

1. Поиск по названию.

В C++ неквалифицированные вызовы функций, такие как operator<< в потоковом выражении, включают в себя unqualified name lookup, который в основном создает набор функций-кандидатов, которые вы можете вызывать. Из этого набора затем выбирается фактическая функция в соответствии с overload resolution rules. Для завершения вызова важно, чтобы (а) предполагаемая функция находилась в наборе кандидатов и (б), что она не является двусмысленной относительно других функций в наборе, учитывая способ вызова функции (количество предоставленных аргументов и их типы, явные параметры шаблона и т. д.). В вашем случае (а) не выполняется.

Проще говоря, поиск operator<< выполняется в три этапа. Во-первых, компилятор ищет член operator<< в классе правого операнда. Таким образом определяются операторы, определенные в потоках Boost.Log. Затем в пространствах имен, содержащих вызов функции, ищет оператор свободной позиции (не являющийся членом), начиная с внутренних пространств имен и перемещаясь наружу. Здесь также рассматриваются имена, импортированные с помощью using директив и деклараций. Этот поиск заканчивается, как только будет найдено operator<<. Обратите внимание, что рассмотренные пространства имен различаются, когда вы вызываете оператора из вашего кода и когда оператор вызывается из Boost.Log, когда вы используете log::add_value. В первом случае вы импортируете my_space в текущее пространство имен, поэтому ваш оператор найден. В последнем случае Boost.Log не импортирует ваше пространство имен, поэтому ваш оператор не найден.

Наконец, компилятор выполняет зависящий от аргумента поиск (ADL) для сбора дополнительной функции, которую вы можете вызвать. В принципе, он ищет оператора в ассоциированных пространствах имен, которые состоят из:

  • Пространство имен, в котором объявляется тип каждого аргумента в вызове функции.Это означает, что пространство имен std рассматривается в обоих случаях, потому что там объявлено std::exception. Когда используется Boost.Log, также рассматривается его внутреннее пространство имен, в котором объявлен тип потока (он содержит несколько операторов, но ни один из них не принимает std::exception).
  • Если функция является шаблоном, также рассматриваются пространства имен его типов аргументов шаблона.
  • Если типы аргументов функции или типы аргументов шаблона являются самими шаблонами, эти пространства имен шаблонов аналогично рассматриваются. Это делается рекурсивно.

АДЛ находит 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()). Это называется 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.Исключение также не обеспечивают operator<< для exception_ptr, поэтому для этого вам придется реализовать пользовательские форматирования.