Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Безопасность исключений


Автор: azesmcar 9.9.2010, 08:51
Добрый день,

Допустим мы пишем функцию-член, в которой происходит трансформация вектора из строк.
Код

class X
{
public:
    /* ... */
    void transform();
private:
    std::vector<std::string> data;
};

Код


void X::transform()
{
    std::transform(data.begin(), data.end(), data.begin(), std::bind2nd(std::plus<std::string>(), "A"));
}

Эта функция не гарантирует строгой безопасности, так-как если вдруг оператор + класса string для 4-ого объекта в векторе сгенерирует исключение, то первые три будут модифицированы, и класс изменит свое состояние. Исправить это легко
Код

void X::transform()
{
    std::vector<std::string> temp(data);
    std::transform(temp.begin(), temp.end(), temp.begin(), std::bind2nd(std::plus<std::string>(), "A"));
    data.swap(temp);
}

но этот вариант намного медленнее.
Если отбросить те моменты, где строгая безопасность исключений явно необходима, то в остальных случаях код подобного рода смахивает на паранойю. Скажем оператор + может сгенерировать bad_alloc, в принципе в этом случае вообще можно наплевать на то, что случиться с программой, все равно продолжать работать в таком состоянии будет невозможно, тут главное помереть с достоинством.
В общем вопрос в том, стоит ли стараться давать строгую гарантию безопасности исключений за счет производительности, в тех случаях, когда этого явно не требуется. И в каких вообще случаях стоит это делать?

Спасибо.

Добавлено через 1 минуту и 57 секунд
Забыл сказать, я за вариант "Только когда это явно требуется".

Автор: mes 9.9.2010, 09:06
Цитата(azesmcar @  9.9.2010,  07:51 Найти цитируемый пост)
    std::vector<std::string> temp(data);
    std::transform(temp.begin(), temp.end(), temp.begin(), std::bind2nd(std::plus<std::string>(), "A"));
    data.swap(temp);


Цитата(azesmcar @  9.9.2010,  07:51 Найти цитируемый пост)
но этот вариант намного медленнее.

 
а если таk, то ненамного :
Код

   std::vector<std::string> temp(data.size());
   std::transform(data.begin(), data.end(), temp.begin(), std::bind2nd(std::plus<std::string>(), "A"));
   data.swap(temp);

 smile 

Автор: azesmcar 9.9.2010, 09:21
Цитата(mes @  9.9.2010,  09:06 Найти цитируемый пост)
а если таk, то ненамного :

Да, но все равно медленнее (одного дополнительного выделения памяти достаточно), имелась ввиду сама мысль smile 

Автор: borisbn 9.9.2010, 10:03
Цитата(mes @  9.9.2010,  09:06 Найти цитируемый пост)
Это сообщение отредактировал(а) mes - 9.9.2010,  09:09

специально дожидался этого времени ? smile
Цитата(azesmcar @  9.9.2010,  08:51 Найти цитируемый пост)
Забыл сказать, я за вариант "Только когда это явно требуется".

поддерживаю. в данном случае ( std::bind2nd(std::plus<std::string>(), "A") ) это действительно похоже на параною, а вот если там будет, например, вызов функции из dll-ки плагина, то это - вполне оправдано.

Автор: azesmcar 9.9.2010, 10:27
borisbn

Речь не только про исключение bad_alloc. Есть категория исключений, с которыми в большинстве случаев вообще не знаешь что делать. bad_alloc - хороший пример из этой категории. Вопрос на самом деле немного более запутанный. Углубимся в детали smile 

допустим мы разрабатываем класс X, который будет проделывать некоторую работу. Класс будет использован нами и возможно в дальнейшем еще кем-то другим, из своей же компании. Поскольку безопасность исключений нельзя оставлять на потом (это может затронуть интерфейс класса) об этом надо подумать заранее. Функция transform нас вполне устраивает и базовой гарантии нам более чем достаточно в нашем конкретном случае, но кто знает в каком контексте будет использован класс Х другими программистами? А если то, что сегодня кажется паранойей завтра кому-то другому может понадобиться? Смысл в том, думать ли о будущем, жертвовать производительностью программы, если на данный момент это не нужно?

Немного изменим пример, сделаем класс X шаблонным. Теперь в него можно передавать любой тип, и то, что кажется паранойей (исключение из оператора +) для класса string может запросто стать обычным явлением в каком нибудь классе A, хотя на сегодняшний день использование класса X с таким типом не предполагается. Но если посмотреть для примера библиотеку STL она для большинства классов дает строгую гарантию безопасности исключений, для большинства, но не для всех и скорее всего из соображений производительности.

Автор: bsa 9.9.2010, 10:51
azesmcar, обычно, это решается путем указания в документации всех известных подводных камней. Т.е. в твоем случае, ты можешь указать, что в случае возникновения исключения класс окажется в неизвестном состоянии. Поэтому использующий его хороший программист будет это уже иметь в виду.

Автор: xvr 9.9.2010, 10:51
Свой вариант - нужно обеспечить, что бы при возникновении исключения не происходило утечек ресурсов и класс (откуда оно вышло) остался в consistent состоянии (т.е. не развалился при его дальнейшем использовании). Отслеживать, что бы класс остался в ПРАВИЛЬНОМ состоянии не требуется, т.к. совершенно непонятно, что под этим ПРАВИЛЬНОМ состоянием понимать.
Т.е. если возьмем исходный пример -
Код

void X::transform()
{
    std::transform(data.begin(), data.end(), data.begin(), std::bind2nd(std::plus<std::string>(), "A"));
}
то он получается безопасным, т.к. хотя data и будет модифицирован, но с ним (модифицированным) можно будет работать и дальше.
Если этот метод отработает до конца (без исключений), data в любом случае будет модифицирован. И нет никакого смысла при возникновении исключения где то посередине работы все откатывать в первоначальное состояние.
Исключение в принципе сигнализирует об ошибке где то в процессе выполнения, и изменение части массива data можно считать частью этой возникшей ошибки

Автор: bsa 9.9.2010, 10:54
Цитата(azesmcar @  9.9.2010,  11:27 Найти цитируемый пост)
если посмотреть для примера библиотеку STL она для большинства классов дает строгую гарантию безопасности исключений, для большинства, но не для всех и скорее всего из соображений производительности.

Думаю, это сделано не для того, чтобы гарантировать тебе безопасность, а просто так получилось при проектировании (т.е. оптимальный алгоритм это гарантирует, а другие - менее эффективны, поэтому не рассматриваются). Или просто необходимо, так как возможное "неизвестное состояние" может привести к краху программы.

Автор: azesmcar 9.9.2010, 11:19
xvr

Цитата(xvr @  9.9.2010,  10:51 Найти цитируемый пост)
нужно обеспечить, что бы при возникновении исключения не происходило утечек ресурсов и класс 

Это базовая гарантия, это само собой как минимум.

http://en.wikipedia.org/wiki/Exception_guarantees
Цитата

    * The strong guarantee: that the operation has either completed successfully or thrown an exception, leaving the program state exactly as it was before the operation started.
    * The basic guarantee: that the invariants of the component are preserved, and no resources are leaked. Often referred to as the weak guarantee, because following an exception the system is left in a safe but unknown state.
    * The no-throw guarantee: that the operation will not throw an exception.

тема про "strong guarantee".

Цитата(bsa @  9.9.2010,  10:51 Найти цитируемый пост)
azesmcar, обычно, это решается путем указания в документации всех известных подводных камней. Т.е. в твоем случае, ты можешь указать, что в случае возникновения исключения класс окажется в неизвестном состоянии. Поэтому использующий его хороший программист будет это уже иметь в виду. 

в документации естественно это должно указываться smile , но вопрос в том, как делать, т.е. ответ - не делать.

Автор: djamshud 9.9.2010, 11:26
Я обычно стараюсь избегать таких ограждений. Только если отдаю на модификацию какие-то важные данные, которые могут понадобиться даже после того, как программа начала умирать. В целом это очень редко, так что ответ между "по требованию" и "никогда!", но ближе к "никогда".

Автор: xvr 9.9.2010, 12:10
Цитата(azesmcar @  9.9.2010,  11:19 Найти цитируемый пост)
Это базовая гарантия, это само собой как минимум.
...
тема про "strong guarantee".

В таком случае будет вариант 2, плавно перетекающий в вариант 3, т.к. лично я не очень вижу массы мест (точнее вообще их не вижу  smile  ), где ТРЕБУЕТСЯ strong guarantee, а не достаточно просто basic guarantee

Автор: azesmcar 9.9.2010, 13:20
Цитата(xvr @  9.9.2010,  12:10 Найти цитируемый пост)
точнее вообще их не вижу

Функция может работать не с памятью а с каким нибудь системным ресурсом, тогда было бы желательно вернуть ему первозданный вид smile 

Интересно, а кто это ЗА 1-ый вариант и без комментариев?

Автор: xvr 9.9.2010, 13:39
Цитата(azesmcar @  9.9.2010,  13:20 Найти цитируемый пост)
тогда было бы желательно вернуть ему первозданный вид
Ну это можно рассматривать как специфичную разновидность утечки ресурсов (ресурс - это не только память). Так что это скорее ближе к basic guarantee



Автор: azesmcar 9.9.2010, 13:43
Цитата(xvr @  9.9.2010,  13:39 Найти цитируемый пост)
Ну это можно рассматривать как специфичную разновидность утечки ресурсов (ресурс - это не только память). Так что это скорее ближе к basic guarantee

в принципе да, но скажем оставленный lock-нутым мьютекс не назовешь утечкой ресурсов.

Автор: bsa 9.9.2010, 13:52
azesmcar, ты же не маленький, ты сам все понимаешь, что мы подразумеваем под "basic", а что под "strong". Естественно, мьютексы и железки надо возвращать в исходное состояние. Более того, тут скорее всего никакого оверхеда по производительности не возникает (только немного по коду). А вот то, о чем ты говорил в первом посте (strong), это уже совсем другое - это значительный оверхед. Более того, местами совершенно ненужный. Поэтому, пользователь твоего класса узнав, что ему предоставляется только basic, уже сам должен решать как поступать дальше - добавлять код для получения strong или нет. Твоя задача, не ставить преграды на его пути (т.е. сделать все необходимое, чтобы он мог воспользоваться стандартной схемой: создать копию, изменить ее, сделать swap с оригиналом)...

Автор: azesmcar 9.9.2010, 13:56
Цитата(bsa @  9.9.2010,  13:52 Найти цитируемый пост)
azesmcar, ты же не маленький, ты сам все понимаешь, что мы подразумеваем под "basic", а что под "strong". Естественно, мьютексы и железки надо возвращать в исходное состояние. Более того, тут скорее всего никакого оверхеда по производительности не возникает (только немного по коду). 

А я по этому поводу и не спрашивал ничего, просто привел пример того, когда может понадобиться строгая гарантия, в ответ на
Цитата(xvr @  9.9.2010,  12:10 Найти цитируемый пост)
точнее вообще их не вижу  



Цитата(bsa @  9.9.2010,  13:52 Найти цитируемый пост)
Твоя задача, не ставить преграды на его пути (т.е. сделать все необходимое, чтобы он мог воспользоваться стандартной схемой: создать копию, изменить ее, сделать swap с оригиналом)... 

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

Цитата(azesmcar @  9.9.2010,  08:51 Найти цитируемый пост)
я за вариант "Только когда это явно требуется". 


но мне все равно интересно как к этому относятся другие.

Автор: xvr 9.9.2010, 16:28
Цитата(azesmcar @  9.9.2010,  13:43 Найти цитируемый пост)
в принципе да, но скажем оставленный lock-нутым мьютекс не назовешь утечкой ресурсов. 
А это уже разрушение внутреннего состояния класса, т.к. мьютех оставлен в состоянии гарантирующим дедлок при повторном вызове метода (попытки залочить мьютех). Так что все равно basic


Автор: icecrashldr 9.9.2010, 17:25
basic
Так как к нему привыкли многие программисты.

Интерестный момент
__try {
   IoCallDriver(); //на драйвер файловой системы, с не инвалидным указателем(IoStackPtr) 
}
__except(1) {
   // Получаем access violation 
   // Здесь будет полный lock файловой системы, кроме текущего потока.
   // Не ядерный отладчик, ни само ядро, обратится не сможет.
   // Получается что надо бы было вернуть Spin lock в обратное состояние, так как что бы обработать его валидно.
   // Что бы продолжить нормальную работу, надо еще раз зайти в эту функцию с валидными данными.
}


Автор: bsa 9.9.2010, 17:34
icecrashldr, WinAPI отличается крайней непродуманностью. И это уже давно всем известно.

Автор: icecrashldr 9.9.2010, 18:28
bsa, Ну это как бы и не WinApi ... smile
Но это место где скорость имеет очень больше значения...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)