c++ question regarding exception safety

Discussion in 'C++' started by unix.sh@gmail.com, Mar 6, 2008.

  1. Guest

    In Section E.3.5. of the book 'the c++ programming language 3rd
    edtion' by Bjarne Stroustrup, why it needs to add the check: 'if (v)'
    in the vector_base destrctor of the vector_base implmentation.

    In my understanding, if this statement failed(v=alloc.allocate(n)) in
    the contrstructor, the destrctor will never be called. So no check
    needed in the destructor.

    But according to the book, Bjarned says clearly in the book(p. 950,
    Section E.3.5)

    Note that this attempt to write safer code complicates the invariant
    for the class: It is no longer
    guaranteed that v points to allocated memory. Now v might be 0 .

    I am confused now.

    Thanks for your help,

    Michael
    , Mar 6, 2008
    #1
    1. Advertising

  2. * :
    > In Section E.3.5. of the book 'the c++ programming language 3rd
    > edtion' by Bjarne Stroustrup, why it needs to add the check: 'if (v)'
    > in the vector_base destrctor of the vector_base implmentation.
    >
    > In my understanding, if this statement failed(v=alloc.allocate(n)) in
    > the contrstructor, the destrctor will never be called. So no check
    > needed in the destructor.
    >
    > But according to the book, Bjarned says clearly in the book(p. 950,
    > Section E.3.5)
    >
    > Note that this attempt to write safer code complicates the invariant
    > for the class: It is no longer
    > guaranteed that v points to allocated memory. Now v might be 0 .
    >
    > I am confused now.


    As I recall, the point was that alloc.allocate(n) does /not/ throw an exception,
    that that constructor does not signal failure by way of an exception but instead
    notes in the object state (e.g. by having a null-pointer value somewhere) that
    it has failed to initialize.

    As Bjarne notes, that complicates the class invariant: it essentially introduces
    a meta-level class invariant, "object has been initialized OR (real class
    invariant)".

    That's known as a zombie object, and they're a common problem in
    garbage-collection based languages like Java and C# that do not have automated
    deterministic destruction. Mostly this problem manifests as complicated
    implementation code (peppered with checks for validity), equally complicated
    client code (peppered with manual cleanup calls).


    Cheers, & hth.,

    - Alf


    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Mar 6, 2008
    #2
    1. Advertising

  3. Guest

    On Mar 6, 12:33 pm, "Alf P. Steinbach" <> wrote:
    > * :
    >
    >
    >
    >
    >
    > > In Section E.3.5. of the book 'the c++ programming language 3rd
    > > edtion' by Bjarne Stroustrup, why it needs to add the check: 'if (v)'
    > > in the vector_base destrctor of the vector_base implmentation.

    >
    > > In my understanding, if this statement failed(v=alloc.allocate(n)) in
    > > the contrstructor, the destrctor will never be called. So no check
    > > needed in the destructor.

    >
    > > But according to the book, Bjarned says clearly in the book(p. 950,
    > > Section E.3.5)

    >
    > > Note that this attempt to write safer code complicates the invariant
    > > for the class: It is no longer
    > > guaranteed that v points to allocated memory. Now v might be 0 .

    >
    > > I am confused now.

    >
    > As I recall, the point was that alloc.allocate(n) does /not/ throw an exception,
    > that that constructor does not signal failure by way of an exception but instead
    > notes in the object state (e.g. by having a null-pointer value somewhere) that
    > it has failed to initialize.
    >
    > As Bjarne notes, that complicates the class invariant: it essentially introduces
    > a meta-level class invariant, "object has been initialized OR (real class
    > invariant)".
    >
    > That's known as a zombie object, and they're a common problem in
    > garbage-collection based languages like Java and C# that do not have automated
    > deterministic destruction.  Mostly this problem manifests as complicated
    > implementation code (peppered with checks for validity), equally complicated
    > client code (peppered with manual cleanup calls).
    >
    > Cheers, & hth.,
    >
    > - Alf
    >
    > --
    > A: Because it messes up the order in which people normally read text.
    > Q: Why is it such a bad thing?
    > A: Top-posting.
    > Q: What is the most annoying thing on usenet and in e-mail?- Hide quoted text -
    >
    > - Show quoted text -


    Thanks a lot for your information.

    I understand the ponit Bjarne tries to express. But my question again
    is what's the difference between the explicit assignment
    v=alloc.allocate(n) and v(alloc.allocate(n)), the later
    initiailization style is being used in the implementation in Section E.
    3.2.(p. 943). According to the book, the later
    one( v(alloc.alocate(n)) in the initializer list) is better than
    v=alloc.allocate(n) in the constructor. In my understanding if
    alloc.allocate(n) doesn't throw exception, v is still a dangling
    pointer, which is the same as v=0. So both of them are the same.

    Thanks,
    Michael
    , Mar 6, 2008
    #3
  4. * :
    > On Mar 6, 12:33 pm, "Alf P. Steinbach" <> wrote:
    >> * :
    >>
    >>
    >>
    >>
    >>
    >>> In Section E.3.5. of the book 'the c++ programming language 3rd
    >>> edtion' by Bjarne Stroustrup, why it needs to add the check: 'if (v)'
    >>> in the vector_base destrctor of the vector_base implmentation.
    >>> In my understanding, if this statement failed(v=alloc.allocate(n)) in
    >>> the contrstructor, the destrctor will never be called. So no check
    >>> needed in the destructor.
    >>> But according to the book, Bjarned says clearly in the book(p. 950,
    >>> Section E.3.5)
    >>> Note that this attempt to write safer code complicates the invariant
    >>> for the class: It is no longer
    >>> guaranteed that v points to allocated memory. Now v might be 0 .
    >>> I am confused now.

    >> As I recall, the point was that alloc.allocate(n) does /not/ throw an exception,
    >> that that constructor does not signal failure by way of an exception but instead
    >> notes in the object state (e.g. by having a null-pointer value somewhere) that
    >> it has failed to initialize.
    >>
    >> As Bjarne notes, that complicates the class invariant: it essentially introduces
    >> a meta-level class invariant, "object has been initialized OR (real class
    >> invariant)".
    >>
    >> That's known as a zombie object, and they're a common problem in
    >> garbage-collection based languages like Java and C# that do not have automated
    >> deterministic destruction. Mostly this problem manifests as complicated
    >> implementation code (peppered with checks for validity), equally complicated
    >> client code (peppered with manual cleanup calls).
    >>
    >> Cheers, & hth.,
    >>
    >> - Alf
    >>
    >> --
    >> A: Because it messes up the order in which people normally read text.
    >> Q: Why is it such a bad thing?
    >> A: Top-posting.
    >> Q: What is the most annoying thing on usenet and in e-mail?- Hide quoted text -
    >>
    >> - Show quoted text -

    >
    > Thanks a lot for your information.
    >
    > I understand the ponit Bjarne tries to express. But my question again
    > is what's the difference between the explicit assignment
    > v=alloc.allocate(n) and v(alloc.allocate(n)), the later
    > initiailization style is being used in the implementation in Section E.
    > 3.2.(p. 943). According to the book, the later
    > one( v(alloc.alocate(n)) in the initializer list) is better than
    > v=alloc.allocate(n) in the constructor. In my understanding if
    > alloc.allocate(n) doesn't throw exception, v is still a dangling
    > pointer, which is the same as v=0. So both of them are the same.
    >
    > Thanks,
    > Michael
    >


    Please don't quote signatures.

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Mar 6, 2008
    #4
  5. James Kanze Guest

    On Mar 6, 6:33 pm, "Alf P. Steinbach" <> wrote:
    > * :


    [...]
    > That's known as a zombie object, and they're a common problem
    > in garbage-collection based languages like Java and C# that do
    > not have automated deterministic destruction.


    You've got me there. How does the lack of automated
    deterministic destruction relate to zombie objects. I would
    have thought that zombie objects occured because of the lack of
    exceptions. (Anything you can do with a destructor in C++, you
    can do with a try block in Java. In client code, of course,
    the try block is usually a lot more painful, but in a
    constructor, I don't think it makes as much difference. And of
    course, there's no relationship between "garbage collection
    based" and "do not have automated deterministic destruction":
    the current proposals are for C++ to have both, and of course,
    there are an awful lot of older languages out there that have
    neither.)

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, Mar 7, 2008
    #5
  6. James Kanze Guest

    On Mar 6, 7:05 pm, wrote:
    > On Mar 6, 12:33 pm, "Alf P. Steinbach" <> wrote:


    [...]
    > I understand the ponit Bjarne tries to express. But my
    > question again is what's the difference between the explicit
    > assignment v=alloc.allocate(n) and v(alloc.allocate(n)), the
    > later initiailization style is being used in the
    > implementation in Section E. 3.2.(p. 943). According to the
    > book, the later one( v(alloc.alocate(n)) in the initializer
    > list) is better than v=alloc.allocate(n) in the constructor.
    > In my understanding if alloc.allocate(n) doesn't throw
    > exception, v is still a dangling pointer, which is the same as
    > v=0. So both of them are the same.


    It depends more on the type than on explicit initialization or
    not. The point is that once v has been constructed, its
    destructor will be called if the constructor of the containing
    class exits via an exception. If v is a non-class type (e.g. a
    raw pointer), the destructor is a no-op, and nothing happens.
    If v is a class type, it could do some clean-up.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, Mar 7, 2008
    #6
  7. * James Kanze:
    > On Mar 6, 6:33 pm, "Alf P. Steinbach" <> wrote:
    >> * :

    >
    > [...]
    >> That's known as a zombie object, and they're a common problem
    >> in garbage-collection based languages like Java and C# that do
    >> not have automated deterministic destruction.

    >
    > You've got me there. How does the lack of automated
    > deterministic destruction relate to zombie objects.


    You have known that earlier... ;-)

    When you don't have automatic deterministic destruction, and you have some
    resource to release, you have to implement some kind of manual cleanup, e.g. a
    member function destroy() available to client code.

    There's no way to guarantee that destroy() is called exactly once or only when
    the object will no longer be used. Worse, to support code that forgets to call
    destroy(), it's not uncommon to also do cleanup in a finalize() function that
    may be called by the garbage collection. And a common solution is to let the
    cleanup code note in the object's state that it's destroyed, a zombie.

    An example of this monstrosity (googled this up now just for your convenience):
    <url: http://www.javapractices.com/topic/TopicAction.do?Id=43>.

    Problem with that example: abstracting a resource that can become invalid on its
    own, such as a file or db connection, is inherently difficult, so that the
    complexity in that zombie solution doesn't quite properly illustrate the evils
    of zombies. So think about e.g. a window handle instead of a db handle. With
    RAII, however, the checking of zombieness can be automated and centralized by a
    smart pointer instead of having zombie checks peppered throughout the code.


    Cheers, & hth.,

    - Alf

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Mar 7, 2008
    #7
  8. James Kanze Guest

    On 7 mar, 17:43, "Alf P. Steinbach" <> wrote:
    > * James Kanze:


    > > On Mar 6, 6:33 pm, "Alf P. Steinbach" <> wrote:
    > >> * :


    > > [...]
    > >> That's known as a zombie object, and they're a common problem
    > >> in garbage-collection based languages like Java and C# that do
    > >> not have automated deterministic destruction.


    > > You've got me there. How does the lack of automated
    > > deterministic destruction relate to zombie objects.


    > You have known that earlier... ;-)


    > When you don't have automatic deterministic destruction, and
    > you have some resource to release, you have to implement some
    > kind of manual cleanup, e.g. a member function destroy()
    > available to client code.


    I think we're talking about a different context. I don't see
    the relevance with regards to constructors or zombie objects
    here. (In practice, the destroy() function in C++ is the
    destructor. But that's not the issue here.)

    > There's no way to guarantee that destroy() is called exactly
    > once or only when the object will no longer be used. Worse, to
    > support code that forgets to call destroy(), it's not uncommon
    > to also do cleanup in a finalize() function that may be called
    > by the garbage collection. And a common solution is to let
    > the cleanup code note in the object's state that it's
    > destroyed, a zombie.


    Now you've lost me completely. I can't make heads or tails out
    of the above---it seems to mix any number of separate concepts.

    > An example of this monstrosity (googled this up now just for
    > your convenience):
    > <url:http://www.javapractices.com/topic/TopicAction.do?Id=43>.


    > Problem with that example: abstracting a resource that can
    > become invalid on its own, such as a file or db connection, is
    > inherently difficult, so that the complexity in that zombie
    > solution doesn't quite properly illustrate the evils of
    > zombies.


    OK. We agree there. But my impression is that the problem
    being addressed in all this is that Java doesn't have
    destructors, so you need a finally block. And as I said, within
    the constructor, that's less of a problem than elsewhere,
    because it's within the class code itself---it doesn't introduce
    a constraint on user code. (Thus, my pre-standard array classes
    uses a try block to handle the case where a copy constructor
    threw, rather than some special extra class with a destructor.)

    Note that in both Java and C++, you can have dangling pointers.
    With the difference that you can reliably detect when they are
    used in Java (or in C++ with garbage collection). (Except, of
    course, I've seen damded little Java code that actually tries to
    detect them. Detecting them is the sort of reliability question
    that doesn't seem to interest Java programmers.) But that,
    again, is more or less separate from the problem of cleaning up
    after an error in the constructor.

    > So think about e.g. a window handle instead of a db handle.
    > With RAII, however, the checking of zombieness can be
    > automated and centralized by a smart pointer instead of having
    > zombie checks peppered throughout the code.


    That is, again, a different issue. Although if I understand
    your suggestion correctly, it's an interesting idea---using
    normal C++ semantics for destruction, but using smart pointers
    for memory management. (I'd buy into it more if smart pointers
    could handle cycles easily.)

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, Mar 7, 2008
    #8
  9. * James Kanze:
    > On 7 mar, 17:43, "Alf P. Steinbach" <> wrote:
    >> * James Kanze:

    >
    >>> On Mar 6, 6:33 pm, "Alf P. Steinbach" <> wrote:
    >>>> * :

    >
    >>> [...]
    >>>> That's known as a zombie object, and they're a common problem
    >>>> in garbage-collection based languages like Java and C# that do
    >>>> not have automated deterministic destruction.

    >
    >>> You've got me there. How does the lack of automated
    >>> deterministic destruction relate to zombie objects.

    >
    >> You have known that earlier... ;-)

    >
    >> When you don't have automatic deterministic destruction, and
    >> you have some resource to release, you have to implement some
    >> kind of manual cleanup, e.g. a member function destroy()
    >> available to client code.

    >
    > I think we're talking about a different context. I don't see
    > the relevance with regards to constructors or zombie objects
    > here.


    See below, or earlier in the thread, keeping in mind that a constructor's main
    job is (usually) to establish the class invariant.

    Or perhaps read Bjarne's article that the OP was referring to.

    This all hangs together.


    > (In practice, the destroy() function in C++ is the
    > destructor. But that's not the issue here.)
    >
    >> There's no way to guarantee that destroy() is called exactly
    >> once or only when the object will no longer be used. Worse, to
    >> support code that forgets to call destroy(), it's not uncommon
    >> to also do cleanup in a finalize() function that may be called
    >> by the garbage collection. And a common solution is to let
    >> the cleanup code note in the object's state that it's
    >> destroyed, a zombie.

    >
    > Now you've lost me completely. I can't make heads or tails out
    > of the above---it seems to mix any number of separate concepts.


    Yes, this all hangs together.


    >> An example of this monstrosity (googled this up now just for
    >> your convenience):
    >> <url:http://www.javapractices.com/topic/TopicAction.do?Id=43>.

    >
    >> Problem with that example: abstracting a resource that can
    >> become invalid on its own, such as a file or db connection, is
    >> inherently difficult, so that the complexity in that zombie
    >> solution doesn't quite properly illustrate the evils of
    >> zombies.

    >
    > OK. We agree there. But my impression is that the problem
    > being addressed in all this is that Java doesn't have
    > destructors, so you need a finally block.


    That's one problem that can lead to zombies.


    Cheers, & hth.,

    - Alf

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Mar 7, 2008
    #9
  10. James Kanze Guest

    On 7 mar, 22:56, "Alf P. Steinbach" <> wrote:
    > * James Kanze:
    > > On 7 mar, 17:43, "Alf P. Steinbach" <> wrote:
    > >> * James Kanze:


    > >>> On Mar 6, 6:33 pm, "Alf P. Steinbach" <> wrote:
    > >>>> * :


    > >>> [...]
    > >>>> That's known as a zombie object, and they're a common problem
    > >>>> in garbage-collection based languages like Java and C# that do
    > >>>> not have automated deterministic destruction.


    > >>> You've got me there. How does the lack of automated
    > >>> deterministic destruction relate to zombie objects.


    > >> You have known that earlier... ;-)


    > >> When you don't have automatic deterministic destruction, and
    > >> you have some resource to release, you have to implement some
    > >> kind of manual cleanup, e.g. a member function destroy()
    > >> available to client code.


    > > I think we're talking about a different context. I don't see
    > > the relevance with regards to constructors or zombie objects
    > > here.


    > See below, or earlier in the thread, keeping in mind that a
    > constructor's main job is (usually) to establish the class
    > invariant.


    Right. The constructor's. Deterministic destruction and
    garbage collection aren't really relevant here: the question is
    what happens when for some reason the the constructor cannot
    establish the invariants. If the object "exists" anyway (or
    perhaps more correctly, in C++ standardese, if there is an
    lvalue expression which can refer to it), then you have a
    zombie. Zombies can easily be objects with no destructor
    (although I suspect that the case is rare in standard C++), and
    Zombieism affects local objects (on stack) just like any others;
    dynamic allocation is not necessary for zombies to exist.

    In older C++, before exceptions were introduced, zombies
    couldn't be avoided. If you wrote something like:

    {
    C obj ;
    // ...
    }

    there was no way to avoid obj "existing". So you had to create
    a zombie state, require the client code to check it, verify it
    on each access, etc. The key to not having zombies is
    exceptions (and you can avoid them just as well in Java as in
    C++).

    > Or perhaps read Bjarne's article that the OP was referring to.


    Bjarne's article didn't really mention zombies. It talked about
    cleaning up in the destructor, before (or as a result of)
    throwing the exception. If I understood it correctly, Bjarne
    didn't consider the possibility of having a zombie; he only
    talked about what happens when reporting an error via an
    exception.

    Again, the problem is common to all languages which have
    exceptions. The tools available to solve the problem do vary
    from one language to the next---in Java, you must use a try
    block; in C++, you have the choice between a try block and a
    subobject with a destructor, which will be called once the
    subobject has been fully constructed.

    Having a choice is, of course, a good thing. But the choice the
    programmer makes isn't that critical here---either way, he's
    cleaned up. (I'm talking here about explicitly creating a class
    which has no other reason d'être but to exploit this behavior of
    destructors. Obviously, if the class you are writing is a
    client of another, existing class, then that class should take
    care of all necessary clean-up in its destructor, and the fact
    that you, as a client, don't have to write explicit try/catch
    blocks is a definite advantage.)

    > This all hangs together.


    I still don't see any real relationship. Zombie objects are a
    result of design choices (or in pre-exception days, the lack of
    a possible choice) concerning the constructor. Deterministic
    destruction solves a different set of problems, and garbage
    collection is yet a third issue, orthogonal to the other two.

    > > (In practice, the destroy() function in C++ is the
    > > destructor. But that's not the issue here.)


    > >> There's no way to guarantee that destroy() is called exactly
    > >> once or only when the object will no longer be used. Worse, to
    > >> support code that forgets to call destroy(), it's not uncommon
    > >> to also do cleanup in a finalize() function that may be called
    > >> by the garbage collection. And a common solution is to let
    > >> the cleanup code note in the object's state that it's
    > >> destroyed, a zombie.


    > > Now you've lost me completely. I can't make heads or tails
    > > out of the above---it seems to mix any number of separate
    > > concepts.


    > Yes, this all hangs together.


    How? Before C++ had exceptions, zombie classes were a fact of
    life. Even when no dynamic allocation was involved, and in a
    few odd cases, when no resources at all were involved.

    > >> An example of this monstrosity (googled this up now just for
    > >> your convenience):
    > >> <url:http://www.javapractices.com/topic/TopicAction.do?Id=43>.


    > >> Problem with that example: abstracting a resource that can
    > >> become invalid on its own, such as a file or db connection, is
    > >> inherently difficult, so that the complexity in that zombie
    > >> solution doesn't quite properly illustrate the evils of
    > >> zombies.


    > > OK. We agree there. But my impression is that the problem
    > > being addressed in all this is that Java doesn't have
    > > destructors, so you need a finally block.


    > That's one problem that can lead to zombies.


    How? This is one case where Java and C++ behave almost
    identically:

    {
    MyClass obj = new MyClass ;
    // ...
    }

    in Java behaves exactly like:

    {
    MyClass obj ;
    // ...
    }

    in C++. If the constructor exits via an exception, there is no
    way of accessing the non-existing (or invalid) object. Thus, no
    zombie.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, Mar 8, 2008
    #10
  11. * James Kanze:
    > On 7 mar, 22:56, "Alf P. Steinbach" <> wrote:
    >> * James Kanze:
    >>> On 7 mar, 17:43, "Alf P. Steinbach" <> wrote:
    >>>> * James Kanze:

    >
    >>>>> On Mar 6, 6:33 pm, "Alf P. Steinbach" <> wrote:
    >>>>>> * :

    >
    >>>>> [...]
    >>>>>> That's known as a zombie object, and they're a common problem
    >>>>>> in garbage-collection based languages like Java and C# that do
    >>>>>> not have automated deterministic destruction.

    >
    >>>>> You've got me there. How does the lack of automated
    >>>>> deterministic destruction relate to zombie objects.

    >
    >>>> You have known that earlier... ;-)

    >
    >>>> When you don't have automatic deterministic destruction, and
    >>>> you have some resource to release, you have to implement some
    >>>> kind of manual cleanup, e.g. a member function destroy()
    >>>> available to client code.

    >
    >>> I think we're talking about a different context. I don't see
    >>> the relevance with regards to constructors or zombie objects
    >>> here.

    >
    >> See below, or earlier in the thread, keeping in mind that a
    >> constructor's main job is (usually) to establish the class
    >> invariant.

    >
    > Right. The constructor's. Deterministic destruction and
    > garbage collection aren't really relevant here


    Trying to understand the relevance by maintaining there isn't one, isn't exactly
    the forward way of going about it... :)

    For that matter, trying to understand the connections between A, B and C by
    discussing each in isolation, is not the most fruitful way to proceed.

    It seems that it might help if again I mention class invariants.

    Let's focus on class invariants.

    Questions that might help you:

    * What is the connection between constructors and class invariants?

    * What is the connection between non-deterministic destruction and class
    invariants?

    It is perhaps this latter question that is problematic for you.

    A bad approach for understanding is to start by denying there is any connection.

    A good approach might be to study the Java example I linked to. And I mean
    really study it. For example, try to answer these questions:

    * What is the class invariant in that example?

    * Exactly how does the lack of deterministic destruction, in that example,
    influence the choice of class invariant?

    Cheers, & hth.,

    - Alf

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Mar 8, 2008
    #11
  12. James Kanze Guest

    On 8 mar, 16:34, "Alf P. Steinbach" <> wrote:
    > * James Kanze:
    > > On 7 mar, 22:56, "Alf P. Steinbach" <> wrote:
    > >> * James Kanze:
    > >>> On 7 mar, 17:43, "Alf P. Steinbach" <> wrote:
    > >>>> * James Kanze:


    > >>>>> On Mar 6, 6:33 pm, "Alf P. Steinbach" <> wrote:
    > >>>>>> * :


    > >>>>> [...]
    > >>>>>> That's known as a zombie object, and they're a common problem
    > >>>>>> in garbage-collection based languages like Java and C# that do
    > >>>>>> not have automated deterministic destruction.


    > >>>>> You've got me there. How does the lack of automated
    > >>>>> deterministic destruction relate to zombie objects.


    > >>>> You have known that earlier... ;-)


    > >>>> When you don't have automatic deterministic destruction, and
    > >>>> you have some resource to release, you have to implement some
    > >>>> kind of manual cleanup, e.g. a member function destroy()
    > >>>> available to client code.


    > >>> I think we're talking about a different context. I don't see
    > >>> the relevance with regards to constructors or zombie objects
    > >>> here.


    > >> See below, or earlier in the thread, keeping in mind that a
    > >> constructor's main job is (usually) to establish the class
    > >> invariant.


    > > Right. The constructor's. Deterministic destruction and
    > > garbage collection aren't really relevant here


    > Trying to understand the relevance by maintaining there isn't
    > one, isn't exactly the forward way of going about it... :)


    Ok, but... You can have zombies which don't allocate any
    resources, and don't need destructors. By definition, the
    zombie state is a result of construction. (Or maybe we're using
    different definitions.) You can just as easily have zombies in
    Java as in C++, and the way to avoid them is exactly the same in
    both languages. although their situation with regards to
    deterministic destructors and garbage collection is exactly
    opposite. You could not effectively avoid them in the C++ I
    first learned, before exceptions. Although that C++ was in the
    same position as modern C++ with regards to deterministic
    destructors and garbage collection. So I'm having a very
    difficult time seeing the relationship.

    > For that matter, trying to understand the connections between
    > A, B and C by discussing each in isolation, is not the most
    > fruitful way to proceed.


    True but... The situation here is the same in modern Java and
    modern C++, which differ greatly in both B and C, and it is
    radically different in earlier C++ and modern C++, in which B
    and C were identical.

    > It seems that it might help if again I mention class
    > invariants.


    > Let's focus on class invariants.


    So far, we're in total agreement. The definition I generally
    use for a zombie is an object whose constructor could not
    establish the invariants, and yet in some way "exists". (More
    correctly, one might say that the object doesn't exist, but that
    lvalue expressions which designate it are still possible.)

    > Questions that might help you:


    > * What is the connection between constructors and class invariants?


    > * What is the connection between non-deterministic
    > destruction and class invariants?


    > It is perhaps this latter question that is problematic for you.


    Exactly. About the only relationship I can find between
    destructors and invariants is that it is often necessary for the
    invariants to hold in order to call the destructor.

    > A bad approach for understanding is to start by denying there
    > is any connection.


    If you put it that way. All I can say is that my reaction is
    based on my experience with older C++, modern C++ and Java. The
    first had zombies, the other two don't (when written
    correctly---and probably except for some special cases).

    > A good approach might be to study the Java example I linked
    > to. And I mean really study it. For example, try to answer
    > these questions:


    > * What is the class invariant in that example?


    > * Exactly how does the lack of deterministic destruction, in
    > that example, influence the choice of class invariant?


    OK. But the first thing I see is:

    public DbConnection () {
    //build a connection and assign it to a field
    //elided.. fConnection =
    ConnectionPool.getInstance().getConnection();
    }

    That's the destructor, and the interesting part---the essential
    part, in fact, has been elided. If the constructor throws an
    exception if it cannot establish the invariants, then there can
    be no zombie.

    What can happen, but in this regard, Java is not really
    different from C++, is that you can have dangling pointers to
    the object, i.e. you can have a pointer to the object after it
    has been destroy. But a dangling pointer isn't a zombie, even
    in Java. (And in this case, garbage collection ensures that you
    can detect the error in Java.)

    The other thing that can happen in Java is that it's easier to
    misuse the class, because you don't have deterministic
    destructors. There's no disagreement on that
    point---deterministic destructors are very useful in a certain
    number of frequent scenarios (where object lifetime corresponds
    to automatic lifetime---otherwise, we're back to no real
    difference between Java and C++). But again, it's not a
    question of zombies: C++ makes the use of this class
    significantly easier, but if you use it correctly, it works the
    same in both cases: in Java, try/finally blocks are the
    equivalent of deterministic destructors in C++. With the
    difference that when you combine deterministic destruction AND
    on stack objects (which Java doesn't have either), you reduce
    coupling significantly, since the client code doesn't have to do
    anything (and thus, can't forget doing anything). All of which
    is (I hope) well known---the people working on C# and CLI
    certainly recognized that this was one place where C++ was
    better than Java, regardless of what they thought about the
    rest.

    One final comment on the code, of course---it shows up another
    weakness of Java, and one that isn't generally recognized as
    such by Java programmers: the absense of an assert which stops
    everything. In any really robust code, you'd want an assert in
    the finalization method: if the client code didn't conform to
    the contract, it's broken, and you can't count on the program
    containing it. (But as I've said elsewhere, I think, Java isn't
    designed for writing really robust code.)

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, Mar 8, 2008
    #12
  13. * James Kanze:
    > On 8 mar, 16:34, "Alf P. Steinbach" <> wrote:
    >
    >> A good approach might be to study the Java example I linked
    >> to. And I mean really study it. For example, try to answer
    >> these questions:

    >
    >> * What is the class invariant in that example?

    >
    >> * Exactly how does the lack of deterministic destruction, in
    >> that example, influence the choice of class invariant?

    >
    > OK. But the first thing I see is:
    >
    > public DbConnection () {
    > //build a connection and assign it to a field
    > //elided.. fConnection =
    > ConnectionPool.getInstance().getConnection();
    > }
    >
    > That's the [constructor], and the interesting part---the essential
    > part, in fact, has been elided. If the constructor throws an
    > exception if it cannot establish the invariants, then there can
    > be no zombie.


    The class invariant may be a bit easier to see by examining the following (Java)
    code snippet from the article:

    public void destroy() throws SQLException {
    if (fIsDestroyed) {
    return;
    }
    else{
    if (fConnection != null) fConnection.close();
    fConnection = null;
    //flag that destory has been called, and that
    //no further calls on this object are valid
    fIsDestroyed = true;
    }
    }

    Implied by that code:

    boolean invariantHolds()
    {
    return
    fIsDestroyed ||
    (fConnection == null || isValidConnection( fConnection ));
    }

    The possibility of (fConnection == null) just complicates the picture. It seems
    to be due to the author not being sure whether getConnection() signals failure
    by returning null or throwing an exception. Another possibility might be that
    the author envisions some closeConnection() method in addition to destroy().

    If we assume that getConnection() throws on failure, and there's no additional
    closeConnection() method, then things can become more clear.

    For in that case fConnection can't be null and the class invariant reduces to

    boolean invariantHolds()
    {
    return
    fIsDestroyed || isValidConnection( fConnection );
    }

    which can be rewritten, for clarity, as

    boolean isZombie() { return fIsDestroyed; }

    boolean nonZombieInvariantHolds() { return isValidConnection( fConnection ); }

    boolean invariantHolds() { return isZombie() || nonZombieInvariantHolds(); }

    I hope you're with me so far in this analysis, because there's no point going
    further without agreeing on the above conclusion. Namely, that we have a
    constructor that signals failure (not able to establish class invariant) by
    throwing, that we have something that can reasonably be called a class invariant
    that holds for any constructed object (I find it more clear to refer to that
    something as a /meta/ class invariant, and reserve plain "class invariant" for
    what the function nonZombieInvariantHolds() checks), and yet we have a zombie.
    Point of possible contention: where fIsDestroyed might be set to true, and why.

    Hint: it's not in the constructor.

    In passing, yes, lack of exception support is also a common cause of zombies.
    It can be worked around by always allocating objects dynamically, or via dynamic
    allocation-like syntax. Which is a heavy price to pay in C++.


    Cheers, & hth.,

    - Alf

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Mar 8, 2008
    #13
  14. James Kanze Guest

    On 9 mar, 00:08, "Alf P. Steinbach" <> wrote:
    > * James Kanze:
    > > On 8 mar, 16:34, "Alf P. Steinbach" <> wrote:


    > >> A good approach might be to study the Java example I linked
    > >> to. And I mean really study it. For example, try to answer
    > >> these questions:


    > >> * What is the class invariant in that example?


    > >> * Exactly how does the lack of deterministic destruction, in
    > >> that example, influence the choice of class invariant?


    > > OK. But the first thing I see is:


    > > public DbConnection () {
    > > //build a connection and assign it to a field
    > > //elided.. fConnection =
    > > ConnectionPool.getInstance().getConnection();
    > > }


    > > That's the [constructor], and the interesting part---the essential
    > > part, in fact, has been elided. If the constructor throws an
    > > exception if it cannot establish the invariants, then there can
    > > be no zombie.


    > The class invariant may be a bit easier to see by examining
    > the following (Java) code snippet from the article:


    > public void destroy() throws SQLException {
    > if (fIsDestroyed) {
    > return;
    > }
    > else{
    > if (fConnection != null) fConnection.close();
    > fConnection = null;
    > //flag that destory has been called, and that
    > //no further calls on this object are valid
    > fIsDestroyed = true;
    > }
    > }


    Which doesn't really say anything about the class invarient.
    But that's not really the point, is it. We can guess about the
    class invariant, but the real question in relationship to
    zombies is what the constructor does if it cannot establish it.
    If the class invariant is that the class maintains an open
    connection (which is probably not an acceptable class invariant
    in this particular case, since it can become invalidated during
    the life of the object, even if the client code rigorously
    respects the contract---but for purposes of demonstration, I'll
    accept it), then what does the constructor do if it cannot
    establish this invariant.

    > Implied by that code:


    > boolean invariantHolds()
    > {
    > return
    > fIsDestroyed ||
    > (fConnection == null || isValidConnection( fConnection ));
    > }


    > The possibility of (fConnection == null) just complicates the
    > picture.


    Agreed. I'd probably have merged this and the bool
    fIsDestroyed. Again, however, without seeing the constructor:
    if the invariant doesn't allow it, then the constructor should
    throw if it can't create a valid connection. If the invariant
    does allow for it, then you have to take it into consideration.

    Personally, in the context of this example (i.e. ignoring the
    fact that the connection can become invalidated during the
    lifetime of the object), I'd consider the class invariant:
    ! fIsDestroyed
    && fConnection != NULL
    && isValidConnection( fConnection )
    Of course, fIsDestroyed is just a debug device, and isn't
    conceptually part of the object to begin with. And in C++, you
    might elimate the pointer, and make the connection a member
    object. In which case, the real invariant is just
    isValidConnection( myConnection )

    > It seems to be due to the author not being sure whether
    > getConnection() signals failure by returning null or throwing
    > an exception.


    It seems due to the fact that the author hasn't really decided
    what his invariants are, and so doesn't know whether to treat
    something as an invariant error (i.e. a fatal software
    error---something which would trigger an assertion failure in
    C++), or as a normal state of the object. Until we know this,
    we can't really talk much about whether we might have a zombie
    or not.

    What is certain, of course, is that if the author decides that
    having a valid connection is part of the class invariant, in
    Java and in modern C++, he can terminate the constructor with an
    exception, and the client code can never access an object which
    doesn't meet the invariant.

    > Another possibility might be that the author envisions some
    > closeConnection() method in addition to destroy().


    So you're coming around to my point of view: the author has
    withheld critical information from us---information we need to
    really discuss the issue further.

    Bad design is bad design. Not defining the exact class
    invariants before writing a single line of code is bad design.
    (In this case, of course, the author "elided" many things, so we
    don't know whether this is bad design, or simple elided
    information.)

    > If we assume that getConnection() throws on failure, and
    > there's no additional closeConnection() method, then things
    > can become more clear.


    OK. For purposes of demonstration, I'll accept the idea that
    "has a valid connection" is part of the class invariant (if
    you'll accept to pretend that the connection can't become
    invalid prematurely---we're creating a somewhat artificial
    example for purposes of demonstration; I think we both agree
    that in real life, this particular case would present a some
    additional complications which we are sweeping under the rug).

    > For in that case fConnection can't be null and the class
    > invariant reduces to


    > boolean invariantHolds()
    > {
    > return
    > fIsDestroyed || isValidConnection( fConnection );
    > }


    > which can be rewritten, for clarity, as


    > boolean isZombie() { return fIsDestroyed; }


    > boolean nonZombieInvariantHolds() { return isValidConnection( fConnection ); }


    > boolean invariantHolds() { return isZombie() || nonZombieInvariantHolds(); }


    > I hope you're with me so far in this analysis,


    The problem here is that you've slipped the term "zombie" in
    with no explination.

    I somewhat suspected that part of our problem might be with
    definitions, rather than the underlying principles. My
    interpretation of the "demo" program was that the intent was for
    the destroy() function to terminate object lifetime. In which
    case, all of the logic around fIsDestroyed is debug logic; *if*
    the client code conforms to the contract, then it will never
    call a member function with fIsDestroyed false, and when
    terminate() is called by the system, fIsDestroyed will be true.
    IMHO, the correct way of handling such debug code is with
    assert(), i.e. in case of an error, you bring the system down
    (because you no longer have confidence in the program). Java
    doesn't support such, however, so you do what you can.

    Note that in that case, of course, your "isZombie()" function
    above always returns false.

    IMHO, this is an important distinction. When I speak of a
    zombie, it's something which may occur even when the client code
    is correct, and conforms to the contract. For example, if the
    constructor of this object doesn't throw if it cannot establish
    the connection. It's a state correct client code has to deal
    with (with the emphesis on *correct*).

    What you seem to be getting at is something I've always called a
    dangling pointer. Using a dangling pointer is an error in
    client code.

    I think that there are actually three distinct issues involved
    here, and I find it clearer to give each a separate name:

    -- If the constructor is unable to establish the invariant, but
    still leaves an accessible object, then we have a zombie.
    The solution to this is exceptions.

    -- If the lifetime of the object ends (regardless of how, for
    the moment), but the object is still accessible, then we
    have a dangling pointer (or lvalue expression---but in
    practice, the problem will only occur with pointers or
    references). The solution to this is "don't do it".
    Seriously, the solution is that all concerned parties must
    be notified---there is no general solution in either
    language (although some types of smart pointers may help in
    specific cases).

    Note that Java and C++ are exactly the same in this regard
    (although some Java advocates like to pretend that dangling
    pointers can't exist in the language). With the one proviso
    that you *can* implement serious runtime checking for this
    in Java, but not in C++ (not even in C++ with garbage
    collection, since the dangling pointer can be to an object
    with automatic lifetime). It's a weak proviso, however,
    because in practice, Java programmers don't implement such
    checking, and Java doesn't provide anything you can
    reasonably do (e.g. like abort()) if you detect the error.

    -- Some objects have very deterministic lifetimes, which must
    be terminated at a very specific instant (or as soon as
    possible---but I find the "very specific instant" to be more
    prevelent in my code). C++ provides an "official" language
    mechanism for this: the destructor---even better, when you
    can arrange for this deterministic lifetime to correspond to
    a scope, C++ will call the destructor automatically for
    you---you don't have to depend on the client code not
    forgetting. In Java, all you have is an ad hoc mechanism,
    and it's up to the client code to conform to the contract;
    in the case where lifetime corresponds to a scope, Java does
    have try/finally, which simplifies somewhat the client code,
    but it is still far from the convenience of C++-like
    destructors. When the lifetime doesn't correspond to an
    automatic scope, of course, you must terminate it explicitly
    in both languages (although C++ has the slight advantage of
    having a language sanctified "official" syntax for this; in
    Java, you never know whether you have to call dispose(), or
    destroy(), or what).

    In practice, if you go back some years, you'll find that
    most of the times an object needed automatic lifetime, it
    was only for memory management or for handling locking.
    Java added language based mechanisms to handle those. As
    we've evolved using C++, however, we (or at least I) have
    found that the basic principle can be very useful in a lot
    of other cases---I probably use it more often for
    transaction management (in the largest sense) than for
    either of the original uses. (But then, I use garbage
    collection---otherwise, I suspect that memory management
    would still predominate. And of course, I also use it for
    handling locks, but those uses are generally isolated in a
    very few higher level mechanisms, like a message queue.
    Whereas I use transaction semantics a lot---an object which
    "undoes" everything if the function "commit()" hasn't been
    called on it before the destructor is called.)

    Anyhow, three separate issues, with three different names.

    > because there's no point going further without agreeing on the
    > above conclusion. Namely, that we have a constructor that
    > signals failure (not able to establish class invariant) by
    > throwing, that we have something that can reasonably be called
    > a class invariant that holds for any constructed object (I
    > find it more clear to refer to that something as a /meta/
    > class invariant, and reserve plain "class invariant" for what
    > the function nonZombieInvariantHolds() checks), and yet we
    > have a zombie.


    Would you also call it a zombie in C++ is someone did:

    DbConnection* p = new DbConnection(...) ;
    // ...
    delete p ;
    p->...

    If so, then I think we'll just have to agree to disagree on the
    terminology. If not, what's the difference between this, and:

    DbConnection p = new DbConnection( ... ) ;
    // ...
    p.destroy() ;
    p. ...

    in Java? The only difference I see is syntax.

    > Point of possible contention: where
    > fIsDestroyed might be set to true, and why.


    > Hint: it's not in the constructor.


    The real point of contention is whether fIsDestroyed is
    conceptually part of the object state, or whether it is simply
    debugging code. IMHO, after p.destroy(), you don't have a
    zombie object, you have a dangling pointer. I think you're
    being mislead by the fact that Java doesn't have direct language
    support for managing object lifetime, and C++ does. Where as I
    don't see that as being a real difference---objects have
    lifetimes, and some objects have very deterministic lifetimes,
    regardless of what the language says about them. And a pointer
    or a reference to an object which is no longer alive is a
    dangling pointer---regardless of whether the memory which once
    held that object is available for re-allocation or not.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, Mar 9, 2008
    #14
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John Harrison

    Exception safety in the STL

    John Harrison, Jul 6, 2003, in forum: C++
    Replies:
    5
    Views:
    4,066
    John Harrison
    Jul 8, 2003
  2. Klaus Ahrens

    array exception safety

    Klaus Ahrens, Dec 8, 2003, in forum: C++
    Replies:
    1
    Views:
    334
    tom_usenet
    Dec 8, 2003
  3. Replies:
    0
    Views:
    248
  4. Stephan Beal

    Question regarding safety of a malloc()

    Stephan Beal, Dec 24, 2008, in forum: C Programming
    Replies:
    17
    Views:
    516
  5. Andrew Shepson

    Question regarding safety of a malloc()

    Andrew Shepson, Jun 2, 2010, in forum: C Programming
    Replies:
    6
    Views:
    266
    Nick Keighley
    Jun 3, 2010
Loading...

Share This Page