Как сохранить тип исключения
От: Alexander Pazdnikov  
Дата: 21.06.11 05:33
Оценка:
Здравствуйте, Коллеги.

Помогите, пожалуйста, решить вопрос потери исходного типа исключения. Своих знаний не хватает.
Есть иерархия исключений. У базового класса определён operator<< для сохранения диагностической информации.
При кидании исключения с добавлением информации через operator<< изначальное исключение приводится к базовому, а хочется чтобы осталось то же самое.

P.S. boost::exception использовать не могу, компилятор в проекте gcc 3.2.3 слишком старый, не компилит boost::exception

прикрепил минимальный рабочий пример.

throw range_err(); //   работает нормально


а вот
throw range_err() << "Hello world!"; //  приводит тип исключения к base_error, а хочу чтобы остался range_err, как быть? 


#include <iostream>
#include <sstream>

using namespace std;

/**
 * Базовый класс исключений общ канальных драйверов
 */
struct base_error : public virtual std::exception
{
    template<typename T> base_error& operator<<(const T &str)
    {
        std::ostringstream os;
        os << m_what << str;
        m_what = os.str();
        return *this;
    };

    virtual ~base_error() throw () {};

    virtual const char * what() const throw() { return m_what.c_str(); }
    
protected:
    base_error() : std::exception() {}; // блокируем возможность кидать исключения base_error
private:
    std::string m_what;
};

struct run_error : public base_error {};

struct range_err : public run_error {}; ///< выход за пределы чего-нибудь (например, массива или вектора)

int main(int argc, char *argv[])
{
    // тут нормально
    try
    {
        throw range_err();
    }
    catch(const range_err &e)
    {
        cout << "Good" << endl;
    }


    // тут происходит подмена типа исключения
    try
    {
        throw range_err() << "Hello world!";
    }
    catch(const range_err &e)
    {
        // хочу поймать здесь 
        cout << "Good" << endl;
    }
    catch(const base_error &e)
    {
        // ловлю здесь 
        cout << "base_error, want range_error" << endl;
    }
    return 0;
}
exception operator<<
Re: Как сохранить тип исключения
От: jazzer Россия Skype: enerjazzer
Дата: 21.06.11 06:19
Оценка: 3 (1)
Здравствуйте, Alexander Pazdnikov, Вы писали:

AP>P.S. boost::exception использовать не могу, компилятор в проекте gcc 3.2.3 слишком старый, не компилит boost::exception


AP>прикрепил минимальный рабочий пример.


можно вытащить оператор<< наружу:
template<typename E, typename T>
typename boost::enable_if<
    boost::is_base_of<base_error, E>
  , E >::type
operator<<( E e, const T &str )
{
  std::ostringstream os;
  os << e.m_what << str;
  e.m_what = os.str();
  return e;
};


enable_if проконтролирует, что оператор будет применяться только к наследникам base_error.

Надеюсь, что gcc 3.2.3 сможет это переварить.

ЗЫ Ничего, что у тебя на каждый чих копируется m_what? Я бы сделал отдельное решение для конкатенации всего в строчку (оно еще во многих местах пригодится) и только в самом конце уже засовывал результат в объект исключения.
ЗЗЫ конструктор копирования std::string может бросить исключение, что чревато terminate. Такие классы лучше не использовать внутри исключений.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Как сохранить тип исключения
От: 13akaEagle Россия  
Дата: 21.06.11 06:20
Оценка:
Здравствуйте, Alexander Pazdnikov, Вы писали:

AP> Здравствуйте, Коллеги.


AP>Помогите, пожалуйста, решить вопрос потери исходного типа исключения. Своих знаний не хватает.

AP>Есть иерархия исключений. У базового класса определён operator<< для сохранения диагностической информации.
AP>При кидании исключения с добавлением информации через operator<< изначальное исключение приводится к базовому, а хочется чтобы осталось то же самое.

Убрать operator<< и сделать инициализацию через конструктор?

ЗЫ: Мейрс, кажется, советует делать operator<< свободной функцией.
Re[2]: Как сохранить тип исключения
От: Alexander Pazdnikov  
Дата: 21.06.11 07:57
Оценка:
Здравствуйте, 13akaEagle, Вы писали:

E>Убрать operator<< и сделать инициализацию через конструктор?

Тогда придётся для каждого типа исключения писать реализацию этого конструктора
Правильно понял идею?

struct base_err
{
    base_err(const char *str);
}

struct range_err : public base_err
{
    range_err(const char *str) : base_err(str) {}
};
struct protocol_err : public base_err
{
    protocol_err(const char *str) : base_err(str) {}
};

const char *str = create_str /// на псевдокоде << "Hello world!" << myvar 1 << " : " << "myvar2 = "  << myvar2 ...

throw range_err(str);


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

struct base_err
{
    base_err(const char *str);
}

struct range_err : public base_err {};
struct protocol_err : public base_err {};

throw range_err() << "Hello world!" << myvar 1 << " : " << "myvar2 = "  << myvar2 ...


гораздо меньше кода, проще сопровождать (если ошибок нет )

E>ЗЫ: Мейрс, кажется, советует делать operator<< свободной функцией.

посмотрю.
b
Re: Как сохранить тип исключения
От: Erop Россия  
Дата: 21.06.11 09:00
Оценка:
Здравствуйте, Alexander Pazdnikov, Вы писали:

AP>прикрепил минимальный рабочий пример.


AP>
AP>throw range_err() << "Hello world!"; //  приводит тип исключения к base_error, а хочу чтобы остался range_err, как быть? 
AP>


исключения тут не при чём.
Срезка происходит уже при вычислении выражения
range_err() << "Hello world!";

Посмотри сам, во что это компилируется.

Пути решения разные. Ну, например, можно сделать что-то типа функции со многими параметрами.

class OstreamParam {
public:
    OstreamParam() : impl( 0 ) {}
    OstreamParam( const OstreamParam& other ) { impl = other.impl != 0 ? other.impl->clone( buffer ) : 0; }
    template<typename T>
    OstreamParam( const T& param ) { impl = ::new( buffer ) paramOuter<T>( &param ); }
    //~OstreamParam() { if( impl != 0 ) impl->~base(); } // на самом деле виртуальный деструктор НЕ НУЖЕН

    void OutputParam( std::ostringstream& dst ) const { if( impl != 0 ) impl->outputParam( dst ); }
    
private:
    struct base {
        // virtual ~base() {} // на самом деле виртуальный деструктор НЕ НУЖЕН
        virtual void outputParam( std::ostringstream& ) const = 0;
        virtual base* clone( void* ) const = 0;
    };
    template<typename T> struct paramOuter : base {
        const T* param;
        paramOuter( const T* param_ ) : param( param_ ) { assert( param != 0 ); }
        virtual void outputParam( std::ostringstream& dst ) const { dst << *param; }
        virtual base* clone( void* buffer ) const { return new( buffer ) paramOuter<T>( param ); }
    };
    base* impl;
    char* buffer[sizeof( paramOuter<char> )];
};
std::ostringstream& operator << ( std::ostringstream& dst, const OstreamParam& param )
{
    param.OutputParam( dst );
    return dst;
}

template<typename TException>
void Trow( const OstreamParam& param1 = OstreamParam(), 
    const OstreamParam& param2 = OstreamParam(), 
    const OstreamParam& param3 = OstreamParam(), 
    const OstreamParam& param4 = OstreamParam(), 
    const OstreamParam& param5 = OstreamParam(), 
    const OstreamParam& param6 = OstreamParam(), 
    const OstreamParam& param7 = OstreamParam(), 
    const OstreamParam& param8 = OstreamParam(), 
    const OstreamParam& param9 = OstreamParam(), 
    const OstreamParam& param10 = OstreamParam(), 
    const OstreamParam& param11 = OstreamParam(), 
    const OstreamParam& param12 = OstreamParam() // В общем стока, стока нуна
)
{
    std::ostringstream text_collector;
    param1.OutputParam( text_collector );
    param2.OutputParam( text_collector );
    param3.OutputParam( text_collector );
    param4.OutputParam( text_collector );
    param5.OutputParam( text_collector );
    param6.OutputParam( text_collector );
    param7.OutputParam( text_collector );
    param8.OutputParam( text_collector );
    param9.OutputParam( text_collector );
    param10.OutputParam( text_collector );
    param11.OutputParam( text_collector );
    param12.OutputParam( text_collector );  // Тут лучше, конечно нанять какого-нибудь индуса ;)

    throw Texception( text_collector.str() );
}
// Пример использования:
void foo{
    Throw<range_error>( "ого и ", 48, " угу" );
}

Кстати, интересно, оно всё подставится, или таки реально будет что-то считать?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Уточню.
От: Erop Россия  
Дата: 21.06.11 09:06
Оценка:
Да, что бы не писать в каждом исключении конструктор, можно, например, сделать у базы метод, устанавливающий её текст.
E>
E>template<typename TException>
E>void Trow( const OstreamParam& param1 = OstreamParam(), 
E>    const OstreamParam& param2 = OstreamParam(), 
E>    const OstreamParam& param3 = OstreamParam(), 
E>    const OstreamParam& param4 = OstreamParam(), 
E>    const OstreamParam& param5 = OstreamParam(), 
E>    const OstreamParam& param6 = OstreamParam(), 
E>    const OstreamParam& param7 = OstreamParam(), 
E>    const OstreamParam& param8 = OstreamParam(), 
E>    const OstreamParam& param9 = OstreamParam(), 
E>    const OstreamParam& param10 = OstreamParam(), 
E>    const OstreamParam& param11 = OstreamParam(), 
E>    const OstreamParam& param12 = OstreamParam() // В общем стока, стока нуна
E>)
E>{
E>    std::ostringstream text_collector;
E>    param1.OutputParam( text_collector );
E>    param2.OutputParam( text_collector );
E>    param3.OutputParam( text_collector );
E>    param4.OutputParam( text_collector );
E>    param5.OutputParam( text_collector );
E>    param6.OutputParam( text_collector );
E>    param7.OutputParam( text_collector );
E>    param8.OutputParam( text_collector );
E>    param9.OutputParam( text_collector );
E>    param10.OutputParam( text_collector );
E>    param11.OutputParam( text_collector );
E>    param12.OutputParam( text_collector );  // Тут лучше, конечно нанять какого-нибудь индуса ;)

E>    TException ex;
      ex.SetText( text_collector.str() );
      throw ex; 
E>}

E>
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Как сохранить тип исключения
От: Alexander Pazdnikov  
Дата: 21.06.11 09:46
Оценка:
По-моему, сложновато.
Re[2]: Как сохранить тип исключения
От: Alexander Pazdnikov  
Дата: 21.06.11 10:14
Оценка:
Здравствуйте, jazzer, Вы писали:

J>enable_if проконтролирует, что оператор будет применяться только к наследникам base_error.


J>Надеюсь, что gcc 3.2.3 сможет это переварить.

Ура , он это СМОГ

J>ЗЫ Ничего, что у тебя на каждый чих копируется m_what? Я бы сделал отдельное решение для конкатенации всего в строчку (оно еще во многих местах пригодится) и только в самом конце уже засовывал результат в объект исключения.

J>ЗЗЫ конструктор копирования std::string может бросить исключение, что чревато terminate. Такие классы лучше не использовать внутри исключений.
Да, главное я упустил : поимку исключения по копии,- косяк. СПАСИБО.

Правильно ли понял идею

решение для конкатенации всего в строчку

?

struct stringizer 
{
   template<typename T> 
   friend 
   stringizer& operator<<(const T &val)
  {
    m_str << val;
  }

  /** Получить указатель на строку  */
  shared_ptr<char *> str()
  {
      string str = m_str.str();
      const size_t size = str.size() + 1;

      shared_ptr<char *> ret(new char [size]);

      copy(str.begin, str.end(), ret.get());
      ret.get()[size - 1] = '\0';

      return ret;
  }

private:
  ostringstream m_str;
};

#define STRINGS(output_strings) (stringizer() << output_strings).str()

throw range_err() << STRINGS("Hello world!" << "myvar1 = " << myvar1);


полная реализация сейчас выглядит так

/** 
 * Макрос кидания исключения
 * @param ex объект кидаемого исключения
 */
#define BASE_THROW(ex) throw where(ex, typeid(ex).name(), SHORT_FILE, __FUNCTION__, __LINE__)

/**
 * Базовый класс исключений общ канальных драйверов
 * Наследовать собственные исключения следует от @link run_error
 */
struct base_error : public virtual std::exception
{
    // здесь также для сохранения типа 
    template <typename E>
    friend
    E where(E ex, const char *name, const char *file, const char *func, int line)
    {
        ex.where(name, file, func, line);
        return ex;
    }

    void where(const char *name, const char *file, const char *func, int line)
    {
        std::ostringstream os;
        os << name << "::" << file << "::" << func << "(" << line << ")" << " : " << m_what;
        m_what = os.str();
    }


    template<typename E, typename T>
        friend
        typename boost::enable_if<
        boost::is_base_of<base_error, E>
        , E >::type
        operator<<( E e, const T &str )
        {
            std::ostringstream os;
            os << e.m_what << str;
            e.m_what = os.str();
            return e;
        };


    virtual ~base_error() throw () {};

    virtual const char * what() const throw() { return m_what.c_str(); }
    
    t_ret Retval() const { return m_ret; }
    void Retval(t_ret ret) { m_ret = ret; }
    
protected:
    base_error() : std::exception(), m_ret(ERR_EXCEPTION) {}; // блокируем возможность кидать исключения base_error
private:
    std::string m_what;
    t_ret m_ret;
};

/**
 * Ошибка наша, своеобразный assert() в рантайме
 */
struct bug_error : public base_error {};

/**
 * Ошибка в предусмотренной работы алгоритма
 */
struct run_error : public base_error {};

struct range_err : public run_error {}; ///< выход за пределы чего-нибудь (например, массива или вектора)
struct convert_error : public run_error {}; ///< ошибка конвертации

/**
 * Макрос для кидания исключения bug_error
 * @param операторы вывода в поток допонительной информации
 * Например. THROW_BUG("This is my fault, error string = " << my_protocol_string_error << ", also code = " << my_error_code)
 */
#define THROW_BUG(output_strings) \
    do { bug_error ex; ex << output_strings; BASE_THROW(ex); } while(0)
Re[3]: Как сохранить тип исключения
От: Erop Россия  
Дата: 21.06.11 10:48
Оценка:
Здравствуйте, Alexander Pazdnikov, Вы писали:

AP>По-моему, сложновато.


Есмли у тебя нет никакой библиотеки, чтобы выразить примитив "параметр форматированной строки", то можно зайти с другого конца:

template<typename T>
struct text_builder_impl {
    std::ostringstream TextBuilder;
    const T& Original
    
    text_builder_impl( const T& original ) {}    
    
    template<typename R>
    text_builder_impl& operator << ( const R& r ) { TextBuilder << r; }

    template<typename TRes>
    TRes operator << ( TRes (*m)( text_builder_impl<T>& ) ) { return m( *this ); }
}; 

template<typename T>
text_builder_impl<T> text_builder( const T& original )
{
    return text_builder_impl<T>( original );
}

template<typename T>
T set_text( text_builder_impl<T>& builder )
{ 
    T res( builder.Original );
    res.SetText( builder.TextBuilder.str() )
    return res;
}

template<typename T>
void throw_it( exception_text_builder<T>& builder )
{
    throw set_text( builder );
}



void foo()
{
    text_builder( my_exception() ) << "Hello world " << 555 << " times!!!" << throw_it;
    throw text_builder( my_exception() ) << "Hello world " << 555 << " times!!!" << set_text;
}

Тут есть такая беда, что мы никак не контролируем, что в конце кто-то напишет таки throw_it или set_text или ещё чего.
Но можно сделать RT проверку. Например в конструкторе копии или в деструкторе text_builder_impl.
Типа запоминать в объекте, что у него уже забрали созданный текст, а если не забрали, но уже что-то в него вывели, то assert, например.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Как сохранить тип исключения
От: Erop Россия  
Дата: 21.06.11 11:24
Оценка: 2 (1)
Здравствуйте, Alexander Pazdnikov, Вы писали:
AP> THROW_BUG("This is my fault, error string = " << my_protocol_string_error << ", also code = " << my_error_code)


Ха. Если написание таких кудрявых и нечитабельных макросов никого не парит, то нафига козе баян вообще?
#define THROW_WITH_PARAMS( EXCEPTION, PARAMS ) \
do {\
    std::ostringstream os; \
    os << PARAMS; \
    EXCEPTION ex; \
    ex.SetText( os.str() ); \
    throw ex; \
} while( 0 )
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Как сохранить тип исключения
От: 13akaEagle Россия  
Дата: 21.06.11 12:21
Оценка:
Здравствуйте, Alexander Pazdnikov, Вы писали:


AP>Правильно понял идею?

AP>по-моему, сложнее сопровождать больше кода.
Один раз макрос написать.

E>>ЗЫ: Мейрс, кажется, советует делать operator<< свободной функцией.

AP>посмотрю.
Извиняюсь, это был Саттер в своей задаче 5.3 из "решение сложных задач".
Re[4]: Как сохранить тип исключения
От: Alexander Pazdnikov  
Дата: 21.06.11 15:15
Оценка:
Здравствуйте, Erop, Вы писали:

E>Ха. Если написание таких кудрявых и нечитабельных макросов никого не парит, то нафига козе баян вообще?


Нет, не парит По крайней мере в данном контексте проще использовать макрос, чем идеальное решение на шаблонах. Сугубо личное мнение.
Такой макрос я понимаю лучше, чем кучку вычурного кода на шаблонах

E>
#define THROW_WITH_PARAMS( EXCEPTION, PARAMS ) \
E>do {\
E>    std::ostringstream os; \
E>    os << PARAMS; \
E>    EXCEPTION ex; \
E>    ex.SetText( os.str() ); \
E>    throw ex; \
E>} while( 0 )


Очень логично.
Аналогичное решение мы рассматривали, только тогда придётся два макроса использовать, один с параметрами, другой без.
К сожалению, перегрузки макросов не поддерживается.
Хотя, может оно и правильно. Спасибо, будем голосовать на командном совещании
Re[4]: Как сохранить тип исключения
От: Alexander Pazdnikov  
Дата: 21.06.11 15:19
Оценка:
Здравствуйте, Erop, Вы писали:

E>
#define THROW_WITH_PARAMS( EXCEPTION, PARAMS ) \
E>do {\
E>    std::ostringstream os; \
E>    os << PARAMS; \
E>    EXCEPTION ex; \
E>    ex.SetText( os.str() ); \
E>    throw ex; \
E>} while( 0 )


std::string в теле исключения, как рекомендовал jazzer, заменить на shared_ptr<const char *> ?
Re[5]: Как сохранить тип исключения
От: Erop Россия  
Дата: 21.06.11 15:59
Оценка:
Здравствуйте, Alexander Pazdnikov, Вы писали:

AP>std::string в теле исключения, как рекомендовал jazzer, заменить на shared_ptr<const char *> ?

Возможно я не в курсе, и shared_ptr<const char *> имеет какую-то необычную семнтику?
Может речь шла о shared_ptr<char>?

По идее shared_ptr<char> потребует использовать нестандартную функцию деаллокации (второй параметр конструктора, если я не ошибаюсь), так как функция деаллокации по умолчанию использует delete, а не delete []
Или там для char отдельная фича какая-то?

Я бы, по простому, использовал бы std::vector<string> и не страдал. Всё равно исключения кидаются редко, так что экономить лишнюю аллокацию смысла немного. Если есть смысл, то можно сделать свою "безопасно копируемую" строку...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Как сохранить тип исключения
От: Erop Россия  
Дата: 21.06.11 18:28
Оценка:
Здравствуйте, Alexander Pazdnikov, Вы писали:

AP>Нет, не парит По крайней мере в данном контексте проще использовать макрос, чем идеальное решение на шаблонах. Сугубо личное мнение.

Ну, шаблонное решение проще отлаживать обычно. Хотя от решения, конечно, зависит.
Ну и бывают ценностные выборы, типа того, что "никаких неочевидных макросов"

IMHO, решение с text_builder( my_exception() ) << "param1 : " << param1 << "param2 : " << param2 << throw_it; не такое уж и сложное.


AP>Хотя, может оно и правильно. Спасибо, будем голосовать на командном совещании

Ну удачи.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[6]: Как сохранить тип исключения
От: Erop Россия  
Дата: 21.06.11 18:36
Оценка:
Я в прошлом сообщении допустил ошибку!
Вместо:

E>Я бы, по простому, использовал бы std::vector<string> и не страдал. Всё равно исключения кидаются редко, так что экономить лишнюю аллокацию смысла немного. Если есть смысл, то можно сделать свою "безопасно копируемую" строку...


надо читать

Я бы, по простому, использовал бы shared_ptr<string> и не страдал. Всё равно исключения кидаются редко, так что экономить лишнюю аллокацию смысла немного. Если есть смысл, то можно сделать свою "безопасно копируемую" строку...

, конечно же
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.