Topic: When is a destructor "used"?


Author: "Andrew J. Bromage" <deguerre@gmail.com>
Date: Wed, 28 Mar 2007 20:54:54 CST
Raw View
G'day everyone.

It was suggested that I move this question from c.l.c++.m here, so
here goes.

Here's the code snippet.

    template<typename T>
    class Container
    {
    public:

        Container(T* p_obj = 0) throw() : m_obj(p_obj) {}

        ~Container()
        {
            // Use checked_delete() to avoid undefined behaviour.
            boost::checked_delete(m_obj);
        }

    private:
        T* m_obj;
    };

    class Foo;

    struct Bar1
    {
         Container<Foo> foo1;
         Container<Foo> foo2;

         ~Bar1();
         Bar1() {}  // Should this compile?
    }

    struct Bar2
    {
        Container<Foo> foo1;
        Container<Foo> foo2;

        ~Bar2();
    };

    void test()
    {
        Bar2 bar2(); // How about this?
    }

GNU accepts Bar2 but not Bar1.  Forte accepts them both.

The question boils down to when a destructor is "used".  Obviously,
when
the destructor of Container is only instantiated when it's used; in
this case,
it's for unwinding objects in a constructor if the constructor may
throw.

GNU works out that the implicit constructor of Bar2 can't throw for
two reasons:
It has a trivial body, and the constructor of Container is declared
throw-none (and
this constraint is applied to all specialisations, of course), hence
no exception can
be thrown after a Container has been constructed.  This means that the
constructor of Bar2 doesn't use the destructor.

The only difference between the constructor of Bar1 and Bar2 is that
Bar1 is
explicit, whereas Bar2 is explicit.  Forte applies the same reasoning
to Bar2
as to Bar1, so it accepts Bar1's constructor.  GNU does not accept it,
probably
go to the trouble of determining whether or not the body of the
constructor
throws or not.

The questions are:

1. Which behaviour is "correct"?  Or are they both correct?
2. Is there a defect in the standard?

It seems to me that either behaviour is reasonable.

On one hand, accepting the constructor makes a lot of practical sense;
I
would think that code like this can't be that uncommon, and it
eliminates
a possibly expensive dependency (i.e. the definition of Foo).

In addition, the principle of least surprise arguably applies here.
Merely
making an implicit constructor explicit should not change the meaning
of a program.

On the other hand, you have to ask how much static analysis a
conforming
C++ implementation should be asked to perform.  This case is trivial,
but
the next may not be.

Your thoughts would be appreciated.

Cheers,
Andrew Bromage

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]