Сам я уже давно взял за правило, что если в проекте используются исключения, то собственные классы исключений в проекте должны быть прямо или косвенно унаследованы от std::exception. Прямо унаследованы -- это напрямую от std::exception. Косвенно -- это когда есть какой-то промежуточный класс-родитель, вроде std::runtime_error или some_3rd_party_lib::exception (при этом промежуточный класс-родитель все равно восходит к std::exception (прямо или косвенно)).
Следование этому правилу сильно упрощает жизнь. Т.к. можно просто писать:
try {
... // Какие-то действия.
}
catch( const std::exception & x ) {
... // Здесь мы точно знаем, что все исключения перехвачены.
}
Тогда как если у нас где-то есть выброс класса, не производного от std::exception (а то и вообще какого-нибудь int-а или std::string), то жизнь становится веселее и непредсказуемее.
Выделить можно два момента:
Во-первых, мы можем вообще не предполагать, что исключение какого-то левого типа XYZ у нас может вылететь. Хотя бы потому, что мы имеем дело со сторонней библиотекой X, в которой используется библиотека Y, в которой используется библиотека Z. И начиная с какой-то версии Z появился тип XYZ. Исключения этого типа не перехватили (возможно по недосмотру) в Y, и не перехватывали (понадеясь на Y) в X. Соответственно, мы снаружи X про XYZ вообще не сном, ни духом. И если в нашем коде обработка исключений сделана только через catch(const std::exception &), то у нас появляются серьезные проблемы, т.к. XYZ будет пролетать "насквозь" и, скорее всего, будет убивать наше приложение (или приложение того, кто использует наш код).
Во-вторых, даже если мы перестраховались и сделали в дополнение к catch(const std::exception &) еще и catch(...), то в этом перестраховочном catch мы мало что можем сделать. Даже толком не сможем залогировать тип пойманного исключения и полезную информацию из него.
В общем, сам давно придерживаюсь принципа, что все собственные исключения должны наследоваться от std::exception, и давным-давно не видел нарушений этого принципа в сторонних C++ных библиотеках, с которыми доводилось сталкиваться. Последний раз что-то подобное, ЕМНИП, видел в OTL лет 15 назад. Но там удалось убедить автора сделать специальные настроечные макросы OTL_EXCEPTION_DERIVED_FROM и OTL_EXCEPTION_IS_DERIVED_FROM_STD_EXCEPTION, чтобы otl_exception был наследником std::exception.
Но вот сегодня в дикой природе увидел исключения, не производные от std::exception. Например:
class EndOfStunMsgException {
public:
EndOfStunMsgException() {}
virtual ~EndOfStunMsgException() {}
};
Причем в копирайте там указаны даты 2011, 2012 и 2013 годы. Т.е. сильно после того, как появился стандарт C++98 (до этого формально std::exception в языке не было, так что каждый городил свою иерархию исключений по собственному разумению).
В связи с этим возникает вопрос: ну вот как так-то?
Нужно сказать, что там вообще к C++ному коду есть вопросы. Например, std::string используется, но специфическим образом:
void addSTMessageIntegrity(std::string &uname, std::string &upwd) {
if (!_constructed || !isCommand())
throw WrongStunBufferFormatException();
uint8_t *suname = (uint8_t *)strdup(uname.c_str());
uint8_t *supwd = (uint8_t *)strdup(upwd.c_str());
stun_attr_add_integrity_by_user_short_term_str(_buffer, &_sz, suname, supwd, SHATYPE_SHA1);
free(suname);
free(supwd);
}
Метод принимает строки по неконстантной(!!!) ссылке, но содержимое параметров uname и upwd не меняется. Более-то, зачем-то делается копия их содержимого для вызова stun_attr_add_integrity_by_user_short_term_str. Временная копия, которая затем сразу же удаляется. Но если взглянуть внутрь stun_attr_add_integrity_by_user_short_term_str, то можно увидеть вот это:
int stun_attr_add_integrity_by_user_short_term_str(
uint8_t *buf, size_t *len, const uint8_t *uname, password_t pwd, SHATYPE shatype) {
if (stun_attr_add_str(buf, len, STUN_ATTRIBUTE_USERNAME, uname, (int)strlen((const char *)uname)) < 0)
return -1;
hmackey_t key;
return stun_attr_add_integrity_str(TURN_CREDENTIALS_SHORT_TERM, buf, len, key, pwd, shatype);
}
Т.е. указатель на uint8_t все равно воспринимается как указатель на char (т.е. здесь нет никакой защиты от гипотетических случаев, когда почему-то char имеет другое представление, нежели uint8_t). Да еще и длина строки вычисляется через strlen, хотя изначально у нас есть std::string, длина которого известна через std::string::size().
И, кстати говоря, код возврата stun_attr_add_integrity_by_user_short_term_str в плюсовой addSTMessageIntegrity тупо игнорируется и теряется. Как и потенциальные ошибки strdup.
В общем, я уже давно не доверяю утверждениям "обычно C++ работает медленнее, чем чистый Си", потому что в достатке насмотрелся на случаи подобной пессимизации C++ного кода.
Сожалею, если этот пост получился излишне злым, но пригорело. Я уже несколько лет как безуспешно предлагаю свои услуги в качестве опытного C++разработчика. Такое ощущение, что это нафиг никому не нужно, поголовно у всех "свои крутые C++ специалисты". А как доведется в код заглянуть, так там и функции-простыни по 200 строк, и тотальное отсутствие комментариев, и коды возврата read не проверяются... Ну или вот такое, как показано выше. Ну OK, крутые так крутые.
Проект, фрагменты из которого были приведены в посте, мне использовать не нужно. Просто потребовалось получить представление о STUN, TURN, ICE и пр. Вот в процессе сбора информации и заглянул ненароком.
Комментариев нет:
Отправить комментарий