RAII по сравнению с исключениями

Я помещаю превосходный ответ JLBorges на аналогичный вопрос дословно из cplusplus.com, так как это наиболее краткое объяснение, которое я прочитал по этому вопросу.

] В шаблоне, который мы пишем, есть два типа имен, которые можно использовать - зависимые имена и не зависимые имена. Зависимое имя - это имя, которое зависит от параметра шаблона; неизменяемое имя имеет то же значение, независимо от параметров шаблона.

Например:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

То, что зависит от зависимого имени, может быть чем-то другим для каждого конкретного экземпляра шаблона. Как следствие, шаблоны C ++ подвержены «двухфазному поиску имен». Когда шаблон сначала анализируется (до того, как выполняется какое-либо создание), компилятор просматривает не зависящие имена. Когда происходит конкретное создание шаблона, параметры шаблона известны к тому времени, и компилятор ищет зависимые имена.

На первом этапе анализатор должен знать, является ли зависимое имя именем типа или имени не-типа. По умолчанию зависимым именем считается имя не-типа.

Использовать ключевое слово typename только в объявлениях шаблонов и определениях, приведенных ниже.

blockquote>

у вас есть квалифицированное имя, которое относится к типу и зависит от параметра шаблона.

48
задан 9 revs 23 May 2017 в 11:53
поделиться

6 ответов

Вы не ДОЛЖНЫ выдавать исключение из деструктора.

Примечание: Обновленный к refeclt изменяется в стандарте:

В C++ 03
, Если исключение уже распространяет тогда приложение, завершится.

В C++ 11
, Если деструктор noexcept (значение по умолчанию) тогда, приложение завершится.

следующее основано на C++ 11

, Если исключение выходит noexcept функция, это - реализация, определенная, если стек даже раскручен.

следующее основано на C++ 03

оконечным, я сразу имею в виду остановку. Остановки раскручивания стека. Больше деструкторов не называют. Весь плохой материал. Посмотрите обсуждение здесь.

выдавание исключения из деструктора

я не следую (поскольку в не соглашаются с), Ваша логика, что это заставляет деструктор становиться более сложным.
С корректным использованием интеллектуальных указателей это на самом деле делает деструктор более простым, поскольку все теперь становится автоматическим. Каждый класс потоки его собственный маленький кусочек загадки. Никакая хирургия головного мозга или аэрокосмические исследования здесь. Другая Большая победа для RAII.

Что касается возможности станд.:: uncaught_exception () я указываю на Вас на статья Herb Sutters о том, почему это не работает

18
ответ дан Martin York 26 November 2019 в 19:05
поделиться

Это напоминает мне вопрос от коллеги, когда я объяснил его exception/RAII понятия: "Эй, что исключение я могу бросить, если компьютер выключил?"

Так или иначе, я согласовываю с ответом Martin York RAII по сравнению с исключениями

, Каково соглашение за Исключениями и Деструкторами?

Много функций C++ зависит от неброска деструкторов.

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

Не то, чтобы это не означает, Вы не можете выдать исключения через деструкторы. Просто то, что язык даже не попытается поддерживать это поведение.

, Что произошло бы, если бы это было авторизовано?

Только для забавы, я пытался вообразить его...

В случае Вашему деструктору не удается освободить Ваш ресурс, что Вы сделаете? Ваш объект, вероятно, наполовину разрушен, что Вы сделали бы от "внешней" выгоды с той информацией? Попробовать еще раз? (если да, то, почему, не попробовав еще раз из деструктора?...)

таким образом, если Вы могли бы получить доступ к своему полуразрушенному объекту это так или иначе: Что, если Ваш объект находится на стеке (который является основным способом, которым RAII работает)? Как можно получить доступ к объекту вне его объема?

Отправка ресурса в исключении?

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

Теперь, вообразите что-то забавным:

 void doSomething()
 {
    try
    {
       MyResource A, B, C, D, E ;

       // do something with A, B, C, D and E

       // Now we quit the scope...
       // destruction of E, then D, then C, then B and then A
    }
    catch(const MyResourceException & e)
    {
       // Do something with the exception...
    }
 }

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

, Но...

Отправка НЕСКОЛЬКИХ ресурсов в НЕСКОЛЬКИХ исключениях?

Теперь, если ~D может перестать работать, то ~C может, также. а также ~B и ~A.

С этим простым примером, у Вас есть 4 деструктора, которые перестали работать в "тот же момент" (выход из объема). То, в чем Вы нуждаетесь, не является не выгодой за одним исключением, а выгодой с массивом исключений (давайте надеяться, что код, сгенерированный для этого, не делает... er... бросают).

    catch(const std::vector<MyResourceException> & e)
    {
       // Do something with the vector of exceptions...
       // Let's hope if was not caused by an out-of-memory problem
    }

Позволяют нам перезапускаться (, мне нравится эта музыка... ): Каждым выданным исключением является различное (, потому что причина отличается: Помните, что в C++, исключения не должны происходить из станд.:: исключение ). Теперь, необходимо одновременно обработать четыре исключения. Как Вы могли записать пункты выгоды, обрабатывающие эти четыре исключения их типами, и согласно порядку они были брошены?

И что, если у Вас есть несколько исключений того же типа, брошенного несколькими отказавшее освобождение? И что, если при выделении памяти массивов исключения массивов, программа выходит из памяти и, er..., бросают из исключения памяти?

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

Apprently, разработчики C++ не видели эффективного решения, и просто сократили их потери там.

проблемой не является RAII по сравнению с Исключениями...

нет, проблема состоит в том, что иногда, вещи могут перестать работать так, что ничто не может быть сделано.

работы RAII хорошо за Исключениями, целых некоторые условия соблюдают. Среди них: деструкторы не бросят . , Что Вы видите, поскольку оппозиция является просто угловым случаем единственного шаблона, комбинирующего два "имени": Исключение и RAII

В случае, проблема происходит в деструкторе, мы должны смириться с поражением и спасти то, что может быть спасено : "Соединению с БД не удалось быть освобожденным? Извините. Давайте, по крайней мере, избегать этой утечки памяти и закрыть этот Файл. "

, В то время как шаблон исключения (предположен быть) основная обработка ошибок в C++, это не единственное. Необходимо обработать исключительный (предназначенная игра слов) случаи, когда исключения C++ не являются решением, при помощи других механизмов ошибки/журнала.

, поскольку Вы просто встретили стену на языке, стену никакой другой язык, о котором я знаю или слышал о, прошел правильно, не разрушая дом (попытка C# была достойной, в то время как Java, каждый - все еще шутка, которая причиняет мне боль на стороне... Я не буду даже говорить о языках сценариев, кто перестанет работать на той же проблеме тем же тихим способом).

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

лучшее, которое можно сделать, Вы уже, записало его. Мое собственное предпочтение идет с броском, завершают метод, очистка деструктора неброска ресурсы, не завершенные вручную, и log/messagebox (если возможный) для предупреждения об отказе в деструкторе.

, Возможно, Вы не поднимаете правильный поединок. Вместо "RAII по сравнению с Исключением", это должно быть" Попытка к освобождению ресурсов по сравнению с Ресурсами, которые абсолютно не хотят быть освобожденными, даже когда угрожается разрушением "

:-)

4
ответ дан Community 26 November 2019 в 19:05
поделиться

От исходного вопроса:

Теперь, освобождение (завершение, однако Вы хотите назвать его) может перестать работать, в этом случае исключениями является действительно единственный способ позволить кому-либо наверху знать о нашей проблеме освобождения

Отказ к очистке ресурс, на который любой указывает:

  1. ошибка Программиста, в этом случае, необходимо зарегистрировать отказ, сопровождаемый путем уведомления пользователя или завершения приложения, в зависимости от сценария приложения. Например, освобождая выделение, которое было уже освобождено.

  2. ошибка Средства выделения или недостаток дизайна. Консультируйтесь с документацией. Возможности являются ошибкой, должен, вероятно, там помочь диагностировать ошибки программиста. Посмотрите объект 1 выше.

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

, Например, C++ свободное хранилище имеет оператор без сбоя, удаляют. Другие API (такие как Win32) обеспечивают коды ошибки, но только перестанут работать из-за ошибки программиста или аппаратного отказа, с ошибками, указывающими на условия как повреждение "кучи", или двойной свободный, и т.д.

Что касается неисправимых неблагоприятных условий, возьмут соединение с БД. Если закрытие соединения перестало работать, потому что соединение было отброшено - прохладный, Вы сделаны. Не бросайте! Выделенное соединение (должно) привести к закрытому соединению, таким образом, нет никакой потребности сделать что-либо еще. В любом случае зарегистрируйте сообщение трассировки, чтобы помочь диагностировать проблемы использования. Пример:

class DBCon{
public:
  DBCon() { 
    handle = fooOpenDBConnection();
  }
  ~DBCon() {
    int err = fooCloseDBConnection();
    if(err){
      if(err == E_fooConnectionDropped){
        // do nothing.  must have timed out
      } else if(fooIsCriticalError(err)){
        // critical errors aren't recoverable.  log, save 
        //  restart information, and die
        std::clog << "critical DB error: " << err << "\n";
        save_recovery_information();
        std::terminate();
      } else {
        // log, in case we need to gather this info in the future,
        //  but continue normally.
        std::clog << "non-critical DB error: " << err << "\n";
      }
    }
    // done!
  }
};

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

Редактирование - Добавляет

, Если Вы действительно хотите быть в состоянии сохранить своего рода ссылку на те соединения с БД, которые не могут закрыться - возможно, им не удалось закрыться из-за неустойчивых условий, и требуется повторить позже - тогда можно всегда задерживать очистку:

vector<DBHandle> to_be_closed_later;  // startup reserves space

DBCon::~DBCon(){
  int err = fooCloseDBConnection();
  if(err){
    ..
    else if( fooIsRetryableError(err) ){
      try{
        to_be_closed.push_back(handle);
      } catch (const bad_alloc&){
        std::clog << "could not close connection, err " << err << "\n"
      }
    }
  }
}

Очень не симпатичный, но это могло бы сделать задание для Вас.

8
ответ дан Aaron 26 November 2019 в 19:05
поделиться

Одна вещь, которую я спросил бы, игнорируя вопрос завершения и так далее, что Вы думаете, что соответствующий ответ состоит в том, если Ваша программа не может закрыть свое соединение с БД, или из-за нормального разрушения или исключительного разрушения.

Вы, кажется, исключаете "просто вход" и лишены желания для завершения, поэтому что Вы думаете, что лучшая вещь состоит в том, чтобы сделать?

я думаю, был ли у нас ответ на тот вопрос тогда, у нас была бы лучшая идея того, как продолжить двигаться.

Никакая стратегия не кажется особенно очевидной для меня; кроме чего-либо еще, я действительно не знаю то, что это означает для закрытия соединения с базой данных бросать. Каково состояние соединения, если близко () бросает? Это закрывается, все еще откройтесь, или неопределенный? И если это неопределенно, там какой-либо путь к программе для возвращения к известному состоянию?

сбой деструктора А означает, что не было никакого способа отменить создание объекта; единственный способ возвратить программу известному (безопасному) состоянию состоит в том, чтобы разъединить весь процесс и запуститься.

2
ответ дан DrPizza 26 November 2019 в 19:05
поделиться

Каковы причины, почему Ваше разрушение могло бы перестать работать? Почему бы не смотреть на обработку тех перед фактическим разрушением?

, Например, закрывая соединение с базой данных может быть то, потому что:

  • происходящая Транзакция. (Проверьте станд.:: uncaught_exception () - если это правда, откат, еще фиксируют - это наиболее вероятные желаемые действия, если у Вас нет политики, которая говорит иначе, прежде на самом деле закрыть соединение.)
  • Соединение отбрасывается. (Обнаружьте и проигнорируйте. Сервер будет откатывать автоматически.)
  • Другая ошибка DB. (Зарегистрируйте его так, мы можем исследовать и возможно обработать соответственно в будущем. Который может быть должен обнаружить и проигнорировать. Тем временем попробуйте откат и разъединитесь снова и проигнорируйте все ошибки.)

, Если я понимаю RAII правильно (который я не мог бы), самым главным является свой объем. Таким образом, это не похоже на Вас, ХОТЯТ транзакции, длящиеся дольше, чем объект так или иначе. Это кажется разумным мне, тогда, что Вы хотите гарантировать закрытие как лучше всего, Вы можете. RAII не делает это уникальным - даже без объектов вообще (скажите в C), Вы все еще попытались бы поймать все состояния ошибки и соглашение с ними настолько лучше всего, как Вы можете (который должен иногда игнорировать их). Весь RAII делает вынудить Вас положить весь этот код на единственное место, неважно, сколько функций использует тот тип ресурса.

1
ответ дан Tanktalus 26 November 2019 в 19:05
поделиться

Можно сказать, существует ли в настоящее время исключение в полете (например, мы между броском и ловим раскручивание стопки выполнения блока, возможно, копируя объекты исключения, или подобный) путем проверки

bool std::uncaught_exception()

, Если это возвращает true, бросающий в эту точку завершит программу, В противном случае безопасно бросить (или по крайней мере столь же безопасный, как это когда-либо). Это обсуждено в Разделе 15.2 и 15.5.3 из ISO 14882 (стандарт C++).

Это не отвечает на вопрос того, что сделать, когда Вы поражаете ошибку при чистке исключения, но действительно нет никаких хороших ответов на это. Но это действительно позволяет Вам различать нормальный выход и исключительный выход, если Вы ожидаете, чтобы сделать что-то другое (как log& проигнорируйте его) в последнем случае, вместо простого паникования.

0
ответ дан puetzk 26 November 2019 в 19:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: