throwing dtors...

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Chris M. Thomasson

    #1

    throwing dtors...

    Is it every appropriate to throw in a dtor? I am thinking about a simple
    example of a wrapper around a POSIX file...
    _______________ _______________ _______________ _______________ ____________
    class file {
    FILE* m_handle;

    public:
    // [...];

    ~file() /* throw() */ {
    int const status fclose(m_handle );
    if (status) {
    /* shi% hit the fan:

    /*

    // [what now?]
    }
    }
    };
    _______________ _______________ _______________ _______________ ____________


    How to properly handle `EAGAIN' in dtor? Well, what about any error for that
    matter? I am a C programmer and only code C++ for fun, and some in-house
    projects. If I were really going to create C++ application and release it
    into the wild, well, how would you advise me to handle the case above? I am
    interested in how throwing in a dtor effects dynamic destruction... Would
    something like the following be legal?



    <pseudo code!!!!>
    _______________ _______________ _______________ _______________ ___
    struct throw_from_dtor {
    int const m_status;

    public:
    throw_from_dtor (int const status)
    m_status(status ) {}

    int get_status() const { return m_status; }
    };

    class file {
    FILE* m_handle;

    public:
    // [ctor];

    ~file() {
    int const status = fclose(m_handle );
    if (status) {
    throw throw_from_dtor (status);
    }
    }
    };


    int main() {
    file* f = new file();
    try {
    delete f;
    } catch(throw_fro m_dtor const& e) {
    // handle error from `e.get_status() '
    delete f;
    }
    return 0;
    }
    _______________ _______________ _______________ _______________ ___


    ?


    or what about using smart pointer...



    int main() {
    std::auto_ptr<f ilef;
    try {
    f.reset(new file());
    } catch(throw_fro m_dtor const& e) {
    // handle error from `e.get_status() '
    }
    }



    ?




    Please keep in mind that refusing to not handle an error from `fclose' could
    resule is HORRIBLE things down the road... Think massive data lost...
    Perhaps __permanent__ data-! OUCH!!!

    ;^/

  • Chris M. Thomasson

    #2
    Re: throwing dtors...


    "Chris M. Thomasson" <no@spam.invali dwrote in message
    news:k4XEk.1619 9$hX5.2021@news fe06.iad...
    Is it every appropriate to throw in a dtor? I am thinking about a simple
    example of a wrapper around a POSIX file...
    _______________ _______________ _______________ _______________ ____________
    [...]
    _______________ _______________ _______________ _______________ ____________
    >
    >
    How to properly handle `EAGAIN' in dtor? Well, what about any error for
    that matter? I am a C programmer and only code C++ for fun, and some
    in-house projects. If I were really going to create C++ application and
    release it into the wild, well, how would you advise me to handle the case
    above? I am interested in how throwing in a dtor effects dynamic
    destruction... Would something like the following be legal?
    >
    >
    >
    <pseudo code!!!!>
    _______________ _______________ _______________ _______________ ___
    struct throw_from_dtor {
    int const m_status;
    >
    public:
    throw_from_dtor (int const status)
    m_status(status ) {}
    >
    int get_status() const { return m_status; }
    };
    >
    class file {
    FILE* m_handle;
    >
    public:
    // [ctor];
    >
    ~file() {
    int const status = fclose(m_handle );
    if (status) {
    throw throw_from_dtor (status);
    ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^

    // ummmm! well, stupid me forgot to store `errno' into the
    exception!!!!!!



    throw throw_from_dtor (errno);



    // sorry about the non-sense! ;^(...


    }
    }
    };

    int main() {
    file* f = new file();
    try {
    delete f;
    } catch(throw_fro m_dtor const& e) {
    // handle error from `e.get_status() '
    delete f;
    }
    return 0;
    }
    _______________ _______________ _______________ _______________ ___
    >
    >
    ?
    >
    >
    or what about using smart pointer...
    >
    >
    >
    int main() {
    std::auto_ptr<f ilef;
    try {
    f.reset(new file());
    } catch(throw_fro m_dtor const& e) {
    // handle error from `e.get_status() '
    }
    }
    >
    >
    >
    ?
    >
    >
    >
    >
    Please keep in mind that refusing to not handle an error from `fclose'
    could resule is HORRIBLE things down the road... Think massive data
    lost... Perhaps __permanent__ data-! OUCH!!!
    >
    ;^/

    Comment

    • Chris M. Thomasson

      #3
      Re: throwing dtors...

      "Chris M. Thomasson" <no@spam.invali dwrote in message
      news:k4XEk.1619 9$hX5.2021@news fe06.iad...
      Is it every appropriate to throw in a dtor? I am thinking about a simple
      example of a wrapper around a POSIX file...
      _______________ _______________ _______________ _______________ ____________
      [...]
      _______________ _______________ _______________ _______________ ____________
      [...]
      how would you advise me to handle the case above? I am interested in how
      throwing in a dtor effects dynamic destruction... Would something like the
      following be legal?
      >
      [...]

      I am doing some experimenting, and found that throwing from a dtor
      apparently leaves the object fully intact wrt the memory that makes it up so
      that proper disaster cleanup can indeed be performed... For example, the
      following program goes into infinite loop:
      _______________ _______________ _______________ _______________ __________
      #include <cstdio>

      struct throw_on_dtor {};

      class foo {
      public:
      ~foo() {
      throw throw_on_dtor() ;
      }
      };

      int main(void) {
      foo* f = new foo();
      retry:
      try {
      delete f;
      } catch (throw_on_dtor const& e) {
      std::puts("thro w_on_dtor caught!");
      goto retry;
      }
      return 0;
      }

      _______________ _______________ _______________ _______________ __________




      So, AFAICT, throwing from a dtor will complicate some odd complications.
      However, they can be worked out for sure. This fact that a dtor can throw
      will need to be CLEARY documented indeed. The above highly crude technique
      can be used to solve the fact when a file close is interrupted by a signal
      (e.g., `EINTR'). It can also be used to handle `EAGAIN'... Although, it
      seems eaiser to use placement new when your dealing with a class that can
      throw from its dtor, so that the catch block can actually free memory
      without running the dtor again like delete does... Something like:

      _______________ _______________ _______________ _______________ __________
      #include <cstdio>
      #include <cstdlib>
      #include <new>

      struct throw_on_dtor {};

      class foo {
      public:
      ~foo() {
      throw throw_on_dtor() ;
      }
      };

      int main(void) {
      foo* f = new (std::malloc(si zeof(*f))) foo();
      if (f) {
      retry:
      try {
      f->~foo();
      std::free(f);
      } catch (throw_on_dtor const& e) {
      std::puts("thro w_on_dtor caught! Handling Error...");
      std::free(f);
      }
      }
      return 0;
      }
      _______________ _______________ _______________ _______________ __________



      Humm... The placement new soultion looks like a good match for throwing
      dtors indeed!



      Also, a class which throws from dtors could contain a dtor counter and/or
      flag to detect how many times, if any, the dtor has been invoked; something
      like:



      class foo {
      unsigned m_dtor_invoke; // = 0
      bool m_dtor_throw; // = false;
      public:
      ~foo() {
      ++m_dtor_invoke ;
      if (! m_dtor_throw) {
      m_dtor_throw = true;
      throw throw_on_dtor() ;
      }
      }
      };



      Any thoughts? BTW, try not to flame me too harshly! I am trying to avoid the
      user explicitly calling a close function... Or, is that a great idea wrt
      dealing with any class that has a dtor which calls an API that can fail
      _AND_ such failure indicates something important?


      ;^(...

      Comment

      • Chris M. Thomasson

        #4
        Re: throwing dtors...


        "Chris M. Thomasson" <no@spam.invali dwrote in message
        news:aoXEk.1620 4$hX5.2055@news fe06.iad...
        "Chris M. Thomasson" <no@spam.invali dwrote in message
        news:k4XEk.1619 9$hX5.2021@news fe06.iad...
        >Is it every appropriate to throw in a dtor? I am thinking about a simple
        >example of a wrapper around a POSIX file...
        >______________ _______________ _______________ _______________ _____________
        [...]
        >______________ _______________ _______________ _______________ _____________
        [...]
        >
        >how would you advise me to handle the case above? I am interested in how
        >throwing in a dtor effects dynamic destruction... Would something like
        >the following be legal?
        >>
        [...]
        >
        I am doing some experimenting, and found that throwing from a dtor
        apparently leaves the object fully intact wrt the memory that makes it up
        so that proper disaster cleanup can indeed be performed... For example,
        the following program goes into infinite loop:
        _______________ _______________ _______________ _______________ __________
        #include <cstdio>
        >
        struct throw_on_dtor {};
        >
        class foo {
        public:
        ~foo() {
        throw throw_on_dtor() ;
        }
        };
        >
        int main(void) {
        foo* f = new foo();
        retry:
        try {
        delete f;
        } catch (throw_on_dtor const& e) {
        std::puts("thro w_on_dtor caught!");
        goto retry;
        }
        return 0;
        }
        >
        _______________ _______________ _______________ _______________ __________
        >
        >
        >
        >
        So, AFAICT, throwing from a dtor will complicate some odd complications.
        ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^

        let me rephrase:

        So, AFAICT, throwing from a dtor will _create_ some odd complications.. .




        Humm... I am now thinking that instead of throwing from dtor, all error
        handling should be preformed within dtor... However, what if user wants to
        be informed of any failure case within dtor? Should I provide a simple
        callback function to inform user of such condition? Something like:
        _______________ _______________ _______________ ______________
        class file {
        FILE* m_handle;
        bool (*m_fp_on_dtor_ error) (file&, int);

        public:
        foo(fp_on_dtor_ error fp = NULL)
        : m_fp_on_dtor_er ror(fp) {
        [...]
        };

        ~foo() {
        retry:
        if (! fclose(m_handle )) {
        if (m_fp_on_dtor_e rror) {
        if (m_fp_on_dtor_e rror(*this, errno)) {
        goto retry;
        }
        }
        }
        }
        };
        _______________ _______________ _______________ ______________




        Humm... I need ADVISE!

        ;^o

        Comment

        • anon

          #5
          Re: throwing dtors...

          Chris M. Thomasson wrote:
          Is it every appropriate to throw in a dtor? I am thinking about a simple
          example of a wrapper around a POSIX file...
          Take a look here:

          _______________ _______________ _______________ _______________ ____________
          class file {
          FILE* m_handle;
          >
          public:
          // [...];
          >
          ~file() /* throw() */ {
          int const status fclose(m_handle );
          if (status) {
          /* shi% hit the fan:

          /*
          >
          // [what now?]
          }
          }
          };
          _______________ _______________ _______________ _______________ ____________
          >
          >
          How to properly handle `EAGAIN' in dtor? Well, what about any error for
          that matter? I am a C programmer and only code C++ for fun, and some
          in-house projects. If I were really going to create C++ application and
          release it into the wild, well, how would you advise me to handle the
          case above? I am interested in how throwing in a dtor effects dynamic
          destruction... Would something like the following be legal?
          >
          How would you handle it in C?
          >
          >
          <pseudo code!!!!>
          _______________ _______________ _______________ _______________ ___
          struct throw_from_dtor {
          int const m_status;
          >
          public:
          throw_from_dtor (int const status)
          m_status(status ) {}
          >
          int get_status() const { return m_status; }
          };
          >
          class file {
          FILE* m_handle;
          >
          public:
          // [ctor];
          >
          ~file() {
          int const status = fclose(m_handle );
          if (status) {
          throw throw_from_dtor (status);
          }
          }
          };
          >
          >
          int main() {
          file* f = new file();
          try {
          delete f;
          } catch(throw_fro m_dtor const& e) {
          // handle error from `e.get_status() '
          delete f;
          }
          return 0;
          }
          _______________ _______________ _______________ _______________ ___
          >
          >
          ?
          >
          >
          or what about using smart pointer...
          >
          >
          >
          int main() {
          std::auto_ptr<f ilef;
          try {
          f.reset(new file());
          } catch(throw_fro m_dtor const& e) {
          // handle error from `e.get_status() '
          }
          }
          >
          >
          >
          ?
          >
          These two mains are almost the same (at least they are doing the same thing.
          >
          >
          >
          Please keep in mind that refusing to not handle an error from `fclose'
          could resule is HORRIBLE things down the road... Think massive data
          lost... Perhaps __permanent__ data-! OUCH!!!
          >
          What can you do when fclose fails?

          Comment

          • Chris M. Thomasson

            #6
            Re: throwing dtors...

            "anon" <anon@no.invali dwrote in message
            news:gc1n78$kks $1@news01.versa tel.de...
            Chris M. Thomasson wrote:
            >Is it every appropriate to throw in a dtor? I am thinking about a simple
            >example of a wrapper around a POSIX file...
            >
            Take a look here:
            http://www.parashift.com/c++-faq-lit...html#faq-11.13
            Okay; I will give it a look.

            [...]
            How would you handle it in C?
            [...]
            What can you do when fclose fails?
            Well, it depends on the error:



            For instance, fclose can get interrupted by a signal. In this case, you need
            to reissue the operation; e.g:
            _______________ _______________ _______________ _______________ __
            struct file {
            FILE* handle;
            };

            int file_close(
            struct file* const this
            ) {
            int status;
            do {
            status = fclose(this->handle);
            } while (status == EOF && errno == EINTR);
            return status;
            }
            _______________ _______________ _______________ _______________ __




            Or, what if it returns `EAGAIN', well, this certainly needs to be handled.
            However, it would be better to let the application to handle this, not do it
            implicitly within the `file_close()' function. There are many ways to handle
            this. That's not the problem. The problem is when a retarded program does
            not handle it! IHO, any program that does not explicitly handle errors from
            `fclose()' is severely broken and VERY dangerous. Let me give you a
            example... Imagine a C++ wrapper around a C FILE... Fine. Imagine the dtor
            looks like this:



            class file {
            FILE* m_handle;

            public:
            ~file() throw() {
            fclose(m_handle );
            }
            };



            Fine... Now, a user needs to copy a file to a disk, and destroy the
            original. Okay. It creates two file objects (e.g., src and dest:)



            {
            file src(...);
            file dest(...);

            // Then it performs the copy operation:

            [copy src to dest]
            }



            Now the code-block goes out of scope, and no exceptions were thrown during
            the copy process, HOWEVER, the call to `fclose()' in the dest object
            failed!!!! Well, the user thinks everything is fine because the completely
            retarded ignorant moron file object did not report the fuc%ing error! So the
            user operates in ignorance and happily destroys the original file thinking
            that the file was COMPLETELY copied onto the disk! WRONG! The file was
            partially copied because `fclose()' failed to do its thing and properly
            flush the buffers, or whatever... Now, the missing file data is LOST
            __forever__! OUCH!!!


            This is why its ESSENTIAL to report and handle errors from `fclose()'... If
            `fclose()' fails, you can't be so sure that the file is 100% coherent...


            Any thoughts?

            Comment

            • Chris M. Thomasson

              #7
              Re: throwing dtors...

              "anon" <anon@no.invali dwrote in message
              news:gc1n78$kks $1@news01.versa tel.de...
              Chris M. Thomasson wrote:
              >Is it every appropriate to throw in a dtor? I am thinking about a simple
              >example of a wrapper around a POSIX file...
              >
              Take a look here:
              http://www.parashift.com/c++-faq-lit...html#faq-11.13
              [...]

              Well, AFAICT, it seems like C++ destructors are really not all that good for
              gracefully handling critical shutdown operations in dtors, such as
              `fclose()'. Where am I going wrong? It seems like the only way to avoid
              throwing from a dtor would be doing something along the lines of:


              // example on how to handle EINTR or perhaps even EAGAIN
              _______________ _______________ _______________ _______________ __________
              class file {
              FILE* m_handle;

              // returns true to retry; or false to continue...
              bool (*m_fp_on_fclos e_dtor) (int);

              public:
              file(bool (*fp_on_fclose_ dtor) (int) = NULL, ...)
              : m_fp_on_fclose_ dtor(fp_on_fclo se_dtor) {
              // [...];
              }

              ~file() throw() {
              while (fclose(m_handl e) == EOF) {
              if (m_fp_on_fclose _dtor && !
              m_fp_on_fclose_ dtor(errno)) {
              break;
              }
              }
              }
              };




              static bool on_fclose_dtor( int status) {
              switch (status) {
              case EINTR:
              return true;

              case EAGAIN:
              sleep(1);
              return true;

              default:
              std::terminate( );
              }
              }


              int main() {
              {
              file f(on_fclose_dto r, ...);
              // [...];
              }
              return 0;
              }
              _______________ _______________ _______________ _______________ __________





              Now, if a signal is thrown, or if the operation would block, the operation
              will at least try to gracefully complete. All other errors KILL the program
              dead... Sounds good to me.




              However, this looks like a horrible hack. There has to be a MUCH between way
              indeed!

              :^o

              Comment

              • Paavo Helde

                #8
                Re: throwing dtors...

                "Chris M. Thomasson" <no@spam.invali dkirjutas:
                Is it every appropriate to throw in a dtor? I am thinking about a
                simple example of a wrapper around a POSIX file...
                _______________ _______________ _______________ _______________ ___________
                _ class file {
                FILE* m_handle;
                >
                public:
                // [...];
                >
                ~file() /* throw() */ {
                int const status fclose(m_handle );
                if (status) {
                /* shi% hit the fan:

                /*
                >
                // [what now?]
                }
                }
                };
                _______________ _______________ _______________ _______________ ___________
                _
                >
                >
                How to properly handle `EAGAIN' in dtor? Well, what about any error
                for that matter? I am a C programmer and only code C++ for fun, and
                some in-house projects. If I were really going to create C++
                application and release it into the wild, well, how would you advise
                me to handle the case above? I am interested in how throwing in a dtor
                effects dynamic destruction... Would something like the following be
                legal?
                Throwing from a dtor is not really advisable in C++. It can easily lead
                to duplicate throws during stack unwinding, and calling terminate() as
                the result.

                The C++ RAII model is built up on the assumption that releasing the
                resource always succeeds (or its failure can be ignored by upper levels).
                If this is not the case, then the application logic becomes very complex
                immediately, essentially you are back in C again.

                In any case, I would suggest to move any activity which can fail out of
                the destructor, into a separate member function which has to be called
                explicitly before destroying of the object, possibly from inside a try-
                catch block dealing with errors.

                In regard of this example, for most applications, fclose() failing
                indicates that the disk is full. What can you do about this? Try to
                delete some random other files from the disk? For most applications I
                believe a proper behavior would be to try to log the error somewhere,
                then either continue or abort, depending on the application type.

                If the file integrity is of the most importance, e.g. in case of a
                database program, this has to be managed explicitly anyway by storing
                something like transaction completion markers in the file itself, or
                whatever. I bet this is not trivial.

                hth
                Paavo




                Comment

                • anon

                  #9
                  Re: throwing dtors...

                  Chris M. Thomasson wrote:
                  "anon" <anon@no.invali dwrote in message
                  news:gc1n78$kks $1@news01.versa tel.de...
                  >Chris M. Thomasson wrote:
                  >>Is it every appropriate to throw in a dtor? I am thinking about a
                  >>simple example of a wrapper around a POSIX file...
                  >>
                  >Take a look here:
                  >http://www.parashift.com/c++-faq-lit...html#faq-11.13
                  [...]
                  >
                  Well, AFAICT, it seems like C++ destructors are really not all that good
                  for gracefully handling critical shutdown operations in dtors, such as
                  `fclose()'. Where am I going wrong? It seems like the only way to avoid
                  throwing from a dtor would be doing something along the lines of:
                  >
                  Destructors have to cleanup its objects, and they should (must) not fail.
                  >
                  // example on how to handle EINTR or perhaps even EAGAIN
                  _______________ _______________ _______________ _______________ __________
                  class file {
                  FILE* m_handle;
                  >
                  // returns true to retry; or false to continue...
                  bool (*m_fp_on_fclos e_dtor) (int);
                  >
                  public:
                  file(bool (*fp_on_fclose_ dtor) (int) = NULL, ...)
                  : m_fp_on_fclose_ dtor(fp_on_fclo se_dtor) {
                  // [...];
                  }
                  >
                  ~file() throw() {
                  while (fclose(m_handl e) == EOF) {
                  if (m_fp_on_fclose _dtor && !
                  m_fp_on_fclose_ dtor(errno)) {
                  break;
                  }
                  }
                  }
                  };
                  >
                  >
                  IMO This would be better:

                  class file {
                  FILE* m_handle;

                  public:
                  file()
                  {
                  // [...];
                  }

                  ~file()
                  {
                  try
                  {
                  close_file();
                  }
                  // catch other exceptions
                  catch(...)
                  {
                  // log the error
                  }
                  }

                  void close_file()
                  {
                  // do whatever you can to close the file
                  // throw an exception in a case of an error
                  }

                  };

                  int main()
                  {
                  try
                  {
                  file obj;
                  // do stuff
                  obj.close_file( );
                  }
                  catch(...)
                  {
                  // log error
                  // try to repair the damage
                  }
                  }
                  >
                  Now, if a signal is thrown, or if the operation would block, the
                  operation will at least try to gracefully complete. All other errors
                  KILL the program dead... Sounds good to me.
                  >
                  >
                  With all other errors, your file will not be closed, and you have a
                  terminated program. Not very elegant solution ;)

                  Comment

                  • James Kanze

                    #10
                    Re: throwing dtors...

                    On Oct 2, 5:25 am, "Chris M. Thomasson" <n...@spam.inva lidwrote:
                    Is it every appropriate to throw in a dtor?
                    Sure. There are special cases where the only reason to have a
                    destructor is for it to throw.

                    All such cases are, however, special cases, and objects of those
                    types should only exist in special contexts (typically, as
                    temporaries in a single expression).
                    I am thinking about a simple example of a wrapper around a
                    POSIX file...
                    That one definitly shouldn't throw.
                    _______________ _______________ _______________ _______________ ____________
                    class file {
                    FILE* m_handle;
                    public:
                    // [...];
                    ~file() /* throw() */ {
                    int const status fclose(m_handle );
                    if (status) {
                    /* shi% hit the fan:

                    /*
                    // [what now?]
                    }
                    }
                    };
                    _______________ _______________ _______________ _______________ ____________
                    If you get to the destructor and the file hasn't been closed,
                    it's an error. It should only happen in two cases: your
                    unwinding the stack as a result of another error (which will
                    result in the generated file being deleted, or at least marked
                    as invalid), or there is an error elsewhere in the code (which
                    should result in an assertion failure).
                    How to properly handle `EAGAIN' in dtor? Well, what about any
                    error for that matter? I am a C programmer and only code C++
                    for fun, and some in-house projects. If I were really going to
                    create C++ application and release it into the wild, well, how
                    would you advise me to handle the case above?
                    Require an explicit close by the user, before the object is
                    destructed, and return a return code from that function.

                    FWIW: most of my file output is through a file wrapper class
                    whose destructor deletes the file if it is called before the
                    file is "committed" ; it also has an option for linking several
                    such wrappers, so that all of the files will be deleted unless
                    all have been successfully "committed" . (Also, my shutdown
                    routines flush cout, and generate an error if that fails.)

                    [...]
                    Please keep in mind that refusing to not handle an error from
                    `fclose' could resule is HORRIBLE things down the road...
                    Obviously. And since the error must be handled, you never count
                    on the destructor for the close. (In the normal case---it's
                    fine if you're cleaning up after another error, and are going to
                    delete the file anyway as a result of the other error.)

                    --
                    James Kanze (GABI Software) email:james.kan ze@gmail.com
                    Conseils en informatique orientée objet/
                    Beratung in objektorientier ter Datenverarbeitu ng
                    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

                    Comment

                    • James Kanze

                      #11
                      Re: throwing dtors...

                      On Oct 2, 7:47 am, anon <a...@no.invali dwrote:

                      [...]
                      What can you do when fclose fails?
                      At the very least, you must notify the user. Generally, you'll
                      want to delete the file, and usually return an error code to the
                      OS.

                      About the only exception I can think of is a log file. If
                      closing that fails, you probably want to continue anyway,
                      ignoring the error. (Theoretically, you should log the
                      error:-).)

                      --
                      James Kanze (GABI Software) email:james.kan ze@gmail.com
                      Conseils en informatique orientée objet/
                      Beratung in objektorientier ter Datenverarbeitu ng
                      9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

                      Comment

                      • James Kanze

                        #12
                        Re: throwing dtors...

                        On Oct 2, 9:20 am, "Chris M. Thomasson" <n...@spam.inva lidwrote:
                        "Paavo Helde" <nob...@ebi.eew rote in message
                        [...]
                        For most applications I believe a proper behavior would be
                        to try to log the error somewhere, then either continue or
                        abort, depending on the application type.
                        What if the application needs to copy a file to disk and
                        destroy the original? If `fclose()' fails on the destination
                        file, well, the application won't know about it and will
                        continue on and destroy the source file. Well, the
                        destination file is by definition in a non-coherent state
                        because `fclose()' failed to "do its thing". Well, the lost
                        data is gone forever. A log file will only show why the data
                        was lost, it does not prevent it. In this case I bet the user
                        wished the application just terminated when the `fclose()'
                        failed. Or better, I bet the user would like to be able to
                        catch and explicitly handle this case...
                        This is why it is important in such cases to return an error
                        status (EXIT_FAILURE) from main. A typical idiom in Unix would
                        be something like:

                        program filename tempFile && mv tempFile filename

                        And all too often, the program won't flush standard out and
                        verify that the flush worked. (All to many programs totally
                        ignore the fact that output might fail.) With the result that
                        if you fill up the disk, you loose important data.

                        --
                        James Kanze (GABI Software) email:james.kan ze@gmail.com
                        Conseils en informatique orientée objet/
                        Beratung in objektorientier ter Datenverarbeitu ng
                        9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

                        Comment

                        • Paavo Helde

                          #13
                          Re: throwing dtors...

                          "Chris M. Thomasson" <no@spam.invali dkirjutas:
                          >
                          "Paavo Helde" <nobody@ebi.eew rote in message
                          news:Xns9B2B65D 8AFDA9nobodyebi ee@216.196.97.1 31...
                          >"Chris M. Thomasson" <no@spam.invali dkirjutas:
                          >>
                          >>Is it every appropriate to throw in a dtor? I am thinking about a
                          >>simple example of a wrapper around a POSIX file...
                          >>_____________ _______________ _______________ _______________ ___________
                          >>__
                          [...]
                          >>
                          >Throwing from a dtor is not really advisable in C++. It can easily
                          >lead to duplicate throws during stack unwinding, and calling
                          >terminate() as the result.
                          >>
                          >The C++ RAII model is built up on the assumption that releasing the
                          >resource always succeeds (or its failure can be ignored by upper
                          >levels). If this is not the case, then the application logic becomes
                          >very complex immediately, essentially you are back in C again.
                          >>
                          >In any case, I would suggest to move any activity which can fail out
                          >of the destructor, into a separate member function which has to be
                          >called explicitly before destroying of the object, possibly from
                          >inside a try- catch block dealing with errors.
                          >
                          I think I agree here. Since, IMVHO, at least attempting to gracefully
                          handle `fclose()', such as deferred retrying in the case of `EINTR' or
                          `EAGAIN', is extremely important. Therefore, it sure seems to make
                          sense to force the used to explicitly call a member function which
                          invokes `fclose()' and throws when a very bad error its encountered
                          (e.g., something other than EINTR or EAGAIN).
                          >
                          Exceptions are for communicating situations which cannot be dealt locally
                          to the upper levels in the application logic. If the error can be dealt
                          locally, there is no need for an exception.

                          I am not familiar with EAGAIN, but can't this be handled locally as well?


                          [...]
                          What if the application needs to copy a file to disk and destroy the
                          original? If `fclose()' fails on the destination file, well, the
                          application won't know about it and will continue on and destroy the
                          Apparently, it has to know about this in this case. So fclose() result
                          has to be somehow communicated to the calling application. Unfortunately
                          it cannot be done just by throwing an exception from the destructor.
                          Well, in principle there is the uncaught_except ion() function which one
                          could check in the dtor and decide whether to throw a new exception or if
                          there is one already in the progress, and so no new exception is needed.
                          However, there were some gotchas which made uncaught_except ion() mostly
                          useless IIRC.

                          Paavo

                          Comment

                          • Yannick Tremblay

                            #14
                            Re: throwing dtors...

                            In article <X5_Ek.12946$ex 3.3200@newsfe02 .iad>,
                            Chris M. Thomasson <no@spam.invali dwrote:
                            >"anon" <anon@no.invali dwrote in message
                            >news:gc1n78$kk s$1@news01.vers atel.de...
                            >Chris M. Thomasson wrote:
                            >>Is it every appropriate to throw in a dtor? I am thinking about a simple
                            >>example of a wrapper around a POSIX file...
                            >>
                            >Take a look here:
                            >http://www.parashift.com/c++-faq-lit...html#faq-11.13
                            >
                            >Okay; I will give it a look.
                            >
                            >[...]
                            >
                            >How would you handle it in C?
                            >[...]
                            >
                            >What can you do when fclose fails?
                            >
                            >Well, it depends on the error:
                            >
                            >http://www.opengroup.org/onlinepubs/...sh/fclose.html
                            >
                            >For instance, fclose can get interrupted by a signal. In this case, you need
                            >to reissue the operation; e.g:
                            >______________ _______________ _______________ _______________ ___
                            >struct file {
                            FILE* handle;
                            >};
                            >
                            >int file_close(
                            struct file* const this
                            >) {
                            int status;
                            do {
                            status = fclose(this->handle);
                            } while (status == EOF && errno == EINTR);
                            return status;
                            >}
                            That's precisely your answer:

                            Do exactly what you would do in C in your file_close method
                            and call it from your destructor:

                            File::~File()
                            {
                            if(0 != file_close()) {
                            // log the error
                            // or std::terminate?
                            }
                            }

                            int File::file_clos e()
                            {
                            int status = 0;
                            do {
                            status = fclose(handle);
                            } while (status == EOF && errno == EINTR);
                            return status;
                            }

                            >Or, what if it returns `EAGAIN', well, this certainly needs to be handled.
                            So handle EAGAIN. 2 ways:

                            1- in file_close()
                            2- if impossible in file_close() then you must require that the client do
                            it by explicitely calling file_close() and dealing with errors.

                            >Fine... Now, a user needs to copy a file to a disk, and destroy the
                            >original. Okay. It creates two file objects (e.g., src and dest:)
                            >
                            >{
                            file src(...);
                            file dest(...);
                            >
                            // Then it performs the copy operation:
                            >
                            [copy src to dest]
                            >}
                            >
                            >
                            >
                            >Now the code-block goes out of scope, and no exceptions were thrown during
                            >the copy process, HOWEVER, the call to `fclose()' in the dest object
                            >failed!!!! Well, the user thinks everything is fine because the completely
                            >retarded ignorant moron file object did not report the fuc%ing error! So the
                            >user operates in ignorance and happily destroys the original file thinking
                            >that the file was COMPLETELY copied onto the disk! WRONG! The file was
                            >partially copied because `fclose()' failed to do its thing and properly
                            >flush the buffers, or whatever... Now, the missing file data is LOST
                            >__forever__! OUCH!!!
                            That's a failure of the application

                            foo()
                            {
                            file src(...)
                            file dest(...)
                            dest.copy(src);

                            // Must ensure that dest was correctly flushed/sync/close *before*
                            // deleting src
                            if(0 == dest.file_close ())
                            {
                            src.delete();
                            } else {
                            // whatever is needed to recover
                            }
                            }


                            Comment

                            • Chris M. Thomasson

                              #15
                              Re: throwing dtors...

                              "Chris M. Thomasson" <no@spam.invali dwrote in message
                              news:k4XEk.1619 9$hX5.2021@news fe06.iad...
                              Is it every appropriate to throw in a dtor? I am thinking about a simple
                              example of a wrapper around a POSIX file...
                              [...]

                              Thank you all (e.g., anon, James Kanze and Paavo Helde) for your excellent
                              insight. After absorbing your input, I think the way to go would be
                              something like; thread-safety wrt the `file::m_handle ' member aside for a
                              moment:
                              _______________ _______________ _______________ _______________ _____
                              #if ! defined(FILE_DT OR_UNEXPECTED)
                              # define FILE_DTOR_UNEXP ECTED(mp_file) assert(false)
                              #endif


                              class file {
                              FILE* m_handle;


                              public:
                              struct error {
                              struct base {
                              int const m_status;
                              base(int const status) : m_status(status ) {}
                              };

                              struct bad_descriptor : public base {
                              bad_descriptor( ) : base(EBADF) {}
                              };

                              struct exceeds_offset : public base {
                              exceeds_offset( ) : base(EFBIG) {}
                              };

                              struct process_orphane d : public base {
                              process_orphane d() : base(EIO) {}
                              };

                              struct no_free_space : public base {
                              no_free_space() : base(ENOSPC) {}
                              };

                              struct pipe_not_for_re ading : public base {
                              pipe_not_for_re ading() : base(EPIPE) {}
                              };

                              struct non_existing_de vice : public base {
                              non_existing_de vice() : base(ENXIO) {}
                              };

                              struct already_closed : public base {
                              already_closed( ) : base(0) {}
                              };

                              struct unknown : public base {
                              unknown() : base(0) {}
                              };

                              static void throw_status(in t const status) {
                              assert(! status);
                              switch (status) {
                              case EBADF:
                              throw bad_descriptor( );
                              case EFBIG:
                              throw exceeds_offset( );
                              case EIO:
                              throw process_orphane d();
                              case ENOSPC:
                              throw no_free_space() ;
                              case EPIPE:
                              throw pipe_not_for_re ading();
                              case ENXIO:
                              throw non_existing_de vice();
                              default:
                              throw unknown();
                              }
                              }
                              };


                              private:
                              int prv_close() throw() {
                              int status;
                              do {
                              status = std::fclose(m_h andle);
                              } while (status == EOF && errno == EINTR);
                              return status;
                              }


                              public:
                              bool close(bool handle_eagain = true, unsigned backoff = 1) {
                              if (! m_handle) {
                              throw error::already_ closed();
                              }
                              retry:
                              int status = prv_close();
                              if (status == EOF) {
                              if (errno != EAGAIN) {
                              error::throw_st atus(errno);
                              } else if (handle_eagain) {
                              sleep(backoff);
                              goto retry;
                              }
                              return false;
                              }
                              m_handle = NULL;
                              return true;
                              }


                              file(/* [...] */) : m_handle(NULL) {
                              // [...];
                              }

                              // [...];

                              ~file() throw() {
                              if (m_handle) {
                              if (prv_close() == EOF) {
                              FILE_DTOR_UNEXP ECTED(m_handle) ;
                              }
                              }
                              }
                              };
                              _______________ _______________ _______________ _______________ _____





                              And explicitly document that `file::close()' returns true if everything
                              worked, false if the file was non-blocking and the operation would have
                              blocked, or throws if shi% hit the fan. Then note that `file::close()' only
                              returns false when the `file::close(ha ndle_eagain)' parameter is false.


                              Although James Kanze seems to suggest that `file::close()' should return a
                              error code; perhaps the value from errno. However, this approach requires
                              the user to decipher the errno value and act accordingly. Where the
                              fine-grain exception thing my example code uses decodes errno into specific
                              exception types that the user can catch. AFAICT, I think the fine-grain
                              exceptions are more C++'ish than return specific error codes. The fact that
                              `file::close()' returns a simple bool when a `EAGAIN' is encountered should
                              be okay.


                              Now an application could do something like; taking my file-copy example:
                              _______________ _______________ _______________ _______________ _____
                              void foo() {
                              try {
                              file src(...);
                              file dest(...);
                              dest.copy(src);
                              src.close();
                              dest.close();
                              src.delete();
                              } catch (file::error::b ad_descriptor const& e) {
                              // [...];
                              } catch (file::error::e xceeds_offset const& e) {
                              // [...];
                              } catch (file::error::p rocess_orphaned const& e) {
                              // [...];
                              } catch (file::error::n o_free_space const& e) {
                              // [...];
                              } catch (file::error::p ipe_not_for_rea ding const& e) {
                              // [...];
                              } catch (file::error::n on_existing_dev ice const& e) {
                              // [...];
                              } catch (file::error::a lready_closed const& e) {
                              // [...];
                              } catch (file::error::u nknown const& e) {
                              // [...];
                              }
                              }
                              _______________ _______________ _______________ _______________ _____





                              or if the application does not care which "specific" error caused things to
                              go bad it could do:
                              _______________ _______________ _______________ _______________ _____
                              void foo() {
                              try {
                              file src(...);
                              file dest(...);
                              dest.copy(src);
                              src.close();
                              dest.close();
                              src.delete();
                              } catch (file::error::b ase& e) {
                              // [...];
                              }
                              }
                              _______________ _______________ _______________ _______________ _____





                              Does that look Kosher to you C++ gurus?


                              ;^)

                              Comment

                              Working...