На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела:
1. Название темы - краткое описание кто/что против кого/чего
2. В первом сообщении - список параметров, по которым идет сравнение.
3. Старайтесь аргументировать свои высказывания. Фразы типа "Венда/Слюникс - ацтой" считаются флудом.
4. Давайте жить дружно и не доводить обсуждение до маразма и личных оскорблений.
Модераторы: Модераторы, Комодераторы
  
> C++ RAII/exceptions vs Golang defer/panic/recover
    Я, конечно, помню, что исключения в деструкторах --- это плохо, но всякое ж бывает (возможно, кроме этого, я что-то неправильно написал в C++ коде):

    ExpandedWrap disabled
      #include <iostream>
      #include <stdexcept>
       
      using namespace std;
       
      class Resource
      {
      private:
          string _name;
          bool _exn;
      public:
          Resource(string name, bool exn) {
              _exn = exn;
              _name = name;
              cout << "Open " << _name << endl;
          }
          virtual ~Resource() {
              cout << "Close " << _name << endl;
              if (_exn)
                  throw runtime_error("EXCEPTION: " + _name);
          }
      };
       
      void test()
      {
          Resource a("A", false);
          Resource b("B", true);
          cout << "test" << endl;
      }
       
      int main()
      {
          try {
              test();
          } catch (runtime_error& e) {
              cout << "CATCH: " << e.what() << endl;
          }
          return 0;
      }


    stdout:
    ExpandedWrap disabled
      Open A
      Open B
      test
      Close B


    stderr:
    ExpandedWrap disabled
      terminate called after throwing an instance of 'std::runtime_error'
        what():  EXCEPTION: B


    --- http://ideone.com/zH6e0Q

    Golang:
    ExpandedWrap disabled
      package main
       
      import "fmt"
       
      type Resource struct {
          name string
          exn  bool
      }
       
      func OpenResource(name string, exn bool) (*Resource, error) {
          r := &Resource{name, exn}
          fmt.Println("Open", name)
          return r, nil
      }
       
      func (r *Resource) Close() error {
          fmt.Println("Close", r.name)
          if r.exn {
              panic("EXCEPTION: Close " + r.name)
          }
          return nil
      }
       
      func test() error {
          a, err := OpenResource("A", false)
          if err != nil {
              return err
          }
          defer a.Close()
          b, err := OpenResource("B", true)
          if err != nil {
              return err
          }
          defer b.Close()
          fmt.Println("test")
          return nil
      }
       
      func main() {
          defer rec()
          test()
      }
       
      func rec() {
          if r := recover(); r != nil {
              fmt.Println("CATCH:", r)
          }
      }


    stdout:
    ExpandedWrap disabled
      Open A
      Open B
      test
      Close B
      Close A
      CATCH: EXCEPTION: Close B


    --- http://play.golang.org/p/bG-YnPIGX0
      korvin, видимо, понимаешь не до конца. В деструкторах не должно быть исключений. Совсем.

      Добавлено
      Ну и да, код не аналогичен :) Для аналога того, что написано в Go, тебе нужно реализовать defer(в виде некоторого guard'а), в котором дергать метод close ресурса.
        Цитата D_KEY @
        korvin, видимо, понимаешь не до конца. В деструкторах не должно быть исключений. Совсем.

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

        Цитата D_KEY @
        Ну и да, код не аналогичен :) Для аналога того, что написано в Go, тебе нужно реализовать defer(в виде некоторого guard'а), в котором дергать метод close ресурса.

        Может и не аналогичен, но, пусть это чуть разные механизмы, но они используются для одной и той же цели и с более-менее схожим смыслом.
        Раньше это не мешало сравнивать RAII и try/catch/finally например. =)

        Кстати, как раз недавно наткнулся на реализацию чего-то подобного defer (точнее, документ опубликован ещё до выхода бета-версий Go, так что правильней наоборот, да и вообще, но не суть). Правда там для Java, но всё же.
        Сообщение отредактировано: korvin -
          С этим кодом всё в порядке. В смысле, он, конечно, говнокод, но работать должен, как задумывалось. Что там с вашим g++14, не знаю, а вот Студия, Интел Компилер и g++11:
          ExpandedWrap disabled
            Open A
            Open B
            test
            Close B
            Close A
            CATCH: EXCEPTION: B
          Исключения в деструкторах в любом случае могут возникать, но не должны покидать их пределов. Если исключение покидает пределы деструктора, это не причина программе падать, это лишь означает, что деструктор не закончил работу, и следовательно объект недоразрушен со всеми вытекающими.
          Языком не допускаются только такая ситуация, в которой исключение бросается в тот момент, когда предыдущее ещё не поймано. Потому что если возникает такая ситуация, то эти два исключения невозможно однозначно отсортировать для обработки. Это может произойти только в тот момент, когда на пути от throw до catch выполниться ещё один throw, а это возможно только в деструкторе объекта, лежащего на разматываемой области стека. Т.к. заранее нельзя знать, по какой причине деструктор вызван, поэтому и нельзя допускать исключений, покидающих деструктор от слова вообще.
            Цитата korvin @
            Я, конечно, помню, что исключения в деструкторах --- это плохо, но всякое ж бывает (возможно, кроме этого, я что-то неправильно написал в C++ коде)

            Просто современный компилятор юзаешь.
            ExpandedWrap disabled
              virtual ~Resource() noexcept(false) {

            Цитата Qraizer @
            а вот Студия, Интел Компилер

            Это ожидаемо.
            Цитата Qraizer @
            g++11

            А это был баг в 4.7
              Да ну? Ну посмотри в "15.5.1 The std::terminate() function". Там нет ни одного упоминания исключения в деструкторе без дополнительного специального условия:
              Цитата
              1 In some situations exception handling must be abandoned for less subtle error handling techniques. [ Note: These situations are:
              • when the exception handling mechanism, after completing the initialization of the exception object but before activation of a handler for the exception (15.1), calls a function that exits via an exception, or
              • when the exception handling mechanism cannot find a handler for a thrown exception (15.3), or
              • when the search for a handler (15.3) encounters the outermost block of a function with a noexcept-specification that does not allow the exception (15.4), or
              • when the destruction of an object during stack unwinding (15.2) terminates by throwing an exception, or
              • when initialization of a non-local variable with static or thread storage duration (3.6.2) exits via an exception, or
              • when destruction of an object with static or thread storage duration exits via an exception (3.6.3), or
              • when execution of a function registered with std::atexit or std::at_quick_exit exits via an exception (18.5), or
              • when a throw-expression with no operand attempts to rethrow an exception and no exception is being handled (15.1), or
              • when std::unexpected throws an exception which is not allowed by the previously violated dynamicexception-specification, and std::bad_exception is not included in that dynamic-exception-specification (15.5.2), or
              • when the implementation’s default unexpected exception handler is called (D.11.1), or
              • when the function std::nested_exception::rethrow_nested is called for an object that has captured no exception (18.8.6), or
              • when execution of the initial function of a thread exits via an exception (30.3.1.2), or
              • when the destructor or the copy assignment operator is invoked on an object of type std::thread that refers to a joinable thread (30.3.1.3, 30.3.1.4).
              —end example ]
              Так что да, вполне ожидаемо отсутствие исключения в приведённом выше коде. А что там у вас в C++14 за баги, я не в курсе.
              Сообщение отредактировано: Qraizer -
                Цитата korvin @
                Цитата D_KEY @
                korvin, видимо, понимаешь не до конца. В деструкторах не должно быть исключений. Совсем.

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

                Если есть опасения, значит отлавливай исключения в деструкторе.
                  Зато есть
                  Цитата 15.2 Constructors and destructors
                  3 The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1). [ Note: So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note ]


                  Добавлено
                  Ага. А ещё есть
                  Цитата 15.4 Exception specifications
                  15 A deallocation function (3.7.4.2) with no explicit exception-specification is treated as if it were specified with noexcept(true).
                  так что формально деструктор без явной спецификации исключений считается nothrow. :yes: Как интересно.
                  Сообщение отредактировано: Qraizer -
                    Цитата Qraizer @
                    Да ну?

                    Ну да.
                    Цитата Qraizer @
                    Ну посмотри в "15.5.1 The std::terminate() function"

                    А нахрена мне туда смотреть. Ещё рано. Начать надо с
                    Цитата ISO/IEC 14882:2011 12.4/3
                    A declaration of a destructor that does not have an exception-specification is implicitly considered to have
                    the same exception-specification as an implicit declaration (15.4).

                    Цитата ISO/IEC 14882:2011 15.4/14
                    An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is
                    an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment
                    operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only
                    if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition; f shall
                    allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions
                    if every function it directly invokes allows no exceptions.

                    А вот теперь можете смотреть и думать.
                    Цитата Qraizer @
                    Так что да, вполне ожидаемо отсутствие исключения в приведённом выше коде для тех, кто не знает C++

                    fixed
                    Цитата Qraizer @
                    А что там у вас в C++14 за баги, я не в курсе.

                    У нас всё в порядке. А вот вышеозначенные гранды опять сделали поделки, не выдерживающие элементарной критики.

                    Добавлено
                    Цитата Qraizer @
                    так что формально деструктор без явной спецификации исключений считается nothrow

                    :facepalm: Деточка, deallocation function - это не деструктор.
                      Мальчик, deallocation function – это общее понятие, частным случаем которого являются деструкторы.
                        Цитата Qraizer @
                        Мальчик, deallocation function – это общее понятие, частным случаем которого являются деструкторы.

                        Нам нужен смайлик рука_кирпич...
                        Цитату, сестра, цитату!
                          P.S. std::operator<<<>(std::basic_ostream<>& os, const char* s) не имеет ограничения nothrow.

                          Добавлено
                          Цитата MyNameIsIgor @
                          Нам нужен смайлик рука_кирпич...
                          Сфоткай свой экзерсис. Я с удовольствием посмотрю. Буду сильно надеяться, что последний раз.
                          Сообщение отредактировано: Qraizer -
                            Цитата Qraizer @
                            Сфоткай свой экзерсис. Я с удовольствием посмотрю. Буду сильно надеяться, что последний раз.

                            Цитата Qraizer @
                            std::operator<<<>(std::basic_ostream<>& os, const char* s) не имеет ограничения nothrow.

                            :facepalm: Совсем не читатель?
                            Цитата MyNameIsIgor @
                            a function directly invoked by f’s implicit definition;
                              korvin, можешь кратенько сформулировать суть холивара?

                              Добавлено
                              Цитата
                              C++ RAII/exceptions vs Golang defer/panic/recover

                              У RAII перед defer преимущество, по сути, одно. Освобождение ресурса срабатывает всегда при разрушении объекта, причём это происходит и для полей и для базы(в случае наследования). Далее, defer выразим через RAII посредством guard'ов. RAII через defer не сделать.

                              Что касается исключений, то это отдельный холивар.
                                Цитата D_KEY @
                                korvin, можешь кратенько сформулировать суть холивара?

                                Гм... RAII vs defer (как способы освобождения ресурсов) [vs другие способы освобождения ресурсов (using, with, etc), если есть кому что сказать про них]

                                Цитата D_KEY @
                                У RAII перед defer преимущество, по сути, одно. Освобождение ресурса срабатывает всегда при разрушении объекта, причём это происходит и для полей и для базы(в случае наследования).

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

                                Цитата D_KEY @
                                Далее, defer выразим через RAII посредством guard'ов.

                                Возможно, но будет ли это выражение таким же надёжным или опять его можно будет обойти и ввести систему в некорректное состояние?

                                Цитата D_KEY @
                                RAII через defer не сделать.

                                Да как-то и желания нет.

                                Цитата D_KEY @
                                Что касается исключений, то это отдельный холивар.

                                А исключения тут как пример возможности испортить поведение механизма.
                                  korvin, в C++ ты почти всегда можешь что-то обойти и испортить себе жизнь. Но зачем?

                                  Добавлено
                                  defer(как и with/using/etc.) можно случайно пропустить. RAII же стабилен.
                                    Цитата korvin @
                                    Да, но такие сложные ресурсы могут запутывать и, опять же, в непредвиденном случае возникновения исключения в деструкторе одного из полей, имеем ту же проблему.

                                    Нужно просто не выпускать исключения из деструкторов. Если следовать этому правилу, то непредвиденного не случится, ибо исключения будут ловиться и в деструкторах тех типов, значения которых мы используем в качестве полей.
                                    А вообще на практике довольно сложно получить деструктор, бросающий исключения - для этого необходимы дополнительные телодвижения.

                                    Проблема то в чём - в двух одновременно "летящих" исключениях. Что будет в Go, если в defer произойдёт паника? У тебя в коде обрабатывается только один результат recover().
                                      Мне тут, кстати, подсказали. С noexcept(false) всё работает как надо.
                                        А сообщение Игоря об этом ты не видел?
                                          Цитата MyNameIsIgor @
                                          Проблема то в чём - в двух одновременно "летящих" исключениях. Что будет в Go, если в defer произойдёт паника? У тебя в коде обрабатывается только один результат recover().

                                          Не читал спеку пока что, но судя по экспериментам, если уже одна паника возникла, то следующая заместит собой предыдущую, т.е. при вызове recover после двух паник, он сможет достать только объект последней паники. См. rec2. Соответственно в данном случае, чтобы поймать обе паники, нужно вызывать recover после каждой.

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

                                          Добавлено
                                          Цитата D_KEY @
                                          А сообщение Игоря об этом ты не видел?

                                          Нет. =) Уровень невнимательности 146%
                                          Сообщение отредактировано: korvin -
                                            Цитата korvin @
                                            Да, но такие сложные ресурсы могут запутывать и, опять же, в непредвиденном случае возникновения исключения в деструкторе одного из полей, имеем ту же проблему.

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

                                            Дело не в сложности ресурса. Дело в свободе использования любых объектов. Тебе не нужно задумываться, использует ли объект ресурс.

                                            Добавлено
                                            Цитата korvin @
                                            Цитата D_KEY @
                                            Далее, defer выразим через RAII посредством guard'ов.

                                            Возможно, но будет ли это выражение таким же надёжным или опять его можно будет обойти и ввести систему в некорректное состояние?

                                            Обойти в C++ можно почти все. ССЗБ. Случайно ты это сделать не сможешь.

                                            Добавлено
                                            Цитата korvin @
                                            Цитата D_KEY @
                                            RAII через defer не сделать.

                                            Да как-то и желания нет.

                                            Зато есть желание копипастить один и тот же defer из функции в функцию :D

                                            Добавлено
                                            Цитата korvin @
                                            С noexcept(false) всё работает как надо.

                                            Скорее работает так, как ты ожидаешь. Надеюсь, ты разобрался, почему?

                                            А "как надо" - это не выпускать исключение из деструктора.
                                              Цитата korvin @
                                              Цитата D_KEY @
                                              А сообщение Игоря об этом ты не видел?

                                              Нет. =) Уровень невнимательности 146%
                                              Не удивительно. Пожалуй, следует применять другие методы, нежели копирования поведения. В тематике в частности, ибо он там задалбывает.
                                                Цитата D_KEY @
                                                Даже в книжках по яве есть рекомендации, что "функции освобождения" не должны кидать исключения.

                                                Жаль, что этих книжек не читали те, кто проектировал AutoCloseable :D
                                                  Цитата @@@ @
                                                  Жаль, что этих книжек не читали те, кто проектировал AutoCloseable :D

                                                  Действительно жаль :'(
                                                    Цитата @@@ @
                                                    Жаль, что этих книжек не читали те, кто проектировал AutoCloseable :D

                                                    если бы в "функциях освобождения" можно было бы обойтись без выкидывания исключений, то без них можно было обойтись вообще
                                                      Цитата wind @
                                                      Цитата @@@ @
                                                      Жаль, что этих книжек не читали те, кто проектировал AutoCloseable :D

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

                                                      Без исключений? Во-первых, без них действительно можно обойтись. Во-вторых, если "функциям освобождения" не кидают исключения, то это существенно упрощает построение безопасного в плане исключений кода. В-третьих, каким образом из вашей посылки следует ваш вывод? В С++, например, деструкторы исключений не кидают, при этом исключения могут активно использоваться.
                                                        Цитата D_KEY @
                                                        Во-вторых, если "функциям освобождения" не кидают исключения, то это существенно упрощает построение безопасного в плане исключений кода.
                                                        Не упрощают, а делает возможным. И "в плане исключений" лишнее. Любые состояния ошибок в любых функциях очистки делают код абсолютно ничего не гарантирующим.
                                                          Цитата Qraizer @
                                                          Не упрощают, а делает возможным.

                                                          В C++. В Java исключение в "функции освобождения" ничего не сломает и не приведет к потери информации.

                                                          Цитата
                                                          Любые состояния ошибок в любых функциях очистки делают код абсолютно ничего не гарантирующим.

                                                          Тем не менее, в функциях очистки ошибки могут происходить. Например потому, что API ОС, как правило, предполагает коды ошибок для функций закрытия. В C++ в деструкторе ты можешь(после логирования) либо проигнорировать либо упасть.

                                                          Добавлено
                                                          Цитата D_KEY @
                                                          В Java исключение в "функции освобождения" ничего не сломает и не приведет к потери информации

                                                          Поясню:

                                                          ExpandedWrap disabled
                                                            import java.lang.*;
                                                             
                                                            class A implements AutoCloseable
                                                            {
                                                                public void close()
                                                                {
                                                                    throw new RuntimeException("CloseError");
                                                                }
                                                            }
                                                             
                                                            class Ideone
                                                            {
                                                                public static void main (String[] args) throws java.lang.Exception
                                                                {
                                                                    try(A a = new A())
                                                                    {
                                                                        throw new RuntimeException("MainError");
                                                                    }
                                                                }
                                                            }

                                                          ExpandedWrap disabled
                                                            Exception in thread "main" java.lang.RuntimeException: MainError
                                                                at Ideone.main(Main.java:22)
                                                                Suppressed: java.lang.RuntimeException: CloseError
                                                                    at A.close(Main.java:11)
                                                                    at Ideone.main(Main.java:23)


                                                          http://ideone.com/WRjcGH#stderr

                                                          Естественно, мы сможем сами так же отловить и пройтись по списку подавленных исключений.
                                                          Сообщение отредактировано: D_KEY -
                                                            Цитата D_KEY @
                                                            Тем не менее, в функциях очистки ошибки могут происходить. Например потому, что API ОС, как правило, предполагает коды ошибок для функций закрытия.
                                                            Естественно. Но любая такая хрень означает, что ошибка в программе. Например, почему CloseHandle() может упасть? Разве что из-за испорченнего HANDLE, который потёр кривой поинтер. Или потому, что тот уже закрыт. Когда все подобные ошибки будут исправлены, функции очистки тоже будут успешны.
                                                            Речь о том, что в безошибочной программе функции очистки никогда не столкнутся с такими ситуациями. Поэтому разрабатывать их с учётом подобных ситуаций нет необходимости. Если же такая необходимость возникла, то это значит лишь то, что программа плохо спроектирована, и некоторые действия в ней не должны размещаться в функциях очистки, т.к. не связаны с освобождением ресурсов. Помнишь, например, дискуссию о FreeAndNIL() в контексте Clear(), вызываемой в деструкторе? Там сами дельфисты нагородили костылей и пытались показать, что это нормально.
                                                            Сообщение отредактировано: Qraizer -
                                                              Цитата Qraizer @
                                                              Естественно. Но любая такая хрень означает, что ошибка в программе.

                                                              Проблема в том, что языки, в основном, предоставляют возможность "подавления" таких ошибок, т.к. ошибка может быть в сторонней либе, которую приходится использовать независимо от её кривости (например, если воркэроунд быстрее, проще и дешевле, чем повторная реализация нужного функционала без косяков). В итоге получаем, что ассерты и паники можно таки отловить, нивелируя их цель --- гарантированное падение программы и сигнализация о баге.
                                                                Вообще-то закрытию могут помешать и внешние причины. К примеру, программа открывает файл, скажем на флэшке, и время от времени что-то туда пишет. Пользователь знает, при каких условиях происходит запись, и зная, что больше ничего писаться не будет, выдёргивает флэшку. Это неправильно, но есть такие, что не выполняют отключения устройства. Или флэшка может быть настроена на быстрое извлечение. При попытке закрыть файл произойдёт ошибка. И часть информации при этом потеряется.
                                                                В качестве другого примера можно привести работу с каким-нибудь оборудованием. Часто такое оборудование можно отключить вручную. Правда в таких случаях ошибка не так серьёзна.

                                                                Цитата korvin @
                                                                в основном, предоставляют возможность "подавления" таких ошибок, т.к. ошибка может быть в сторонней либе, которую приходится использовать независимо от её кривости
                                                                Подавление ошибки в таком случае - это костыль, позволяющий пользоваться кривой библиотекой. Но такое подавление само может обеспечить тебе отсутствие гарантий правильной работы программы. Если программист таким способом "борется" с ошибками в программе. За травму, полученную в результате нарушения работником ТБ работодатель ответственности не несёт. И за баги программы возникшие в результате маскировки ошибок библиотеки, ответственность несёт уже не разработчик кривой библиотеки, а тот программист который эти ошибки таким образом спрятал.
                                                                И вообще, эта библиотека, возможно, не предназначалась для такого использования.
                                                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                0 пользователей:


                                                                Рейтинг@Mail.ru
                                                                [ Script execution time: 0,0812 ]   [ 16 queries used ]   [ Generated: 20.04.24, 01:04 GMT ]