Re: Are throwing default constructors bad style, and if so, why?
Gerhard Menzl wrote:
Nevin :-] Liber wrote on throwing default constructors:
I think it is bad mainly because it goes against people's
expectations.
Against which people's expectations? An experienced C++ programmer
should expect any non-trivial operation to throw unless it is explicitly
documented that it doesn't.
If the default constructor of an object can throw, I suddenly have to
"be careful" when using that object as a member in another object,
since the "default constructor can throw" has a ripple effect.
This is the very nature of exceptions. Except for low-level, C-style
code, all code needs to be exception safe. In modern C++, the
possibility of an exception being thrown is the norm, and its absence
is, well, the exception.
Using std::deque for an example, if I have
struct Something
{
//...
std::deque<int> di;
};
In order to make the default constructor of Something non-throwing,
I'd have to write it as:
struct Something
{
Something() {}
Something(Something const& that)
: pdi(that.pdi ? new std::deque<int>(*pdi) : 0)
{}
//...
boost::scoped_ptr< std::deque<int> > pdi;
};
and the associated complexity of pointer semantics (an extra heap
allocation, test for NULL pointer on access, custom copy constructor,
etc.) instead of the object directly.
What's the advantage of a non-throwing default constructor, and of
deferring the construction of the deque? As soon as you want to use
Something, you will trigger the delayed operation, and then you have to
be prepared for exceptions anyway.
I'm highly surprised by this behavior of deque. I'd have hoped that STL
containers at least abide to a no-throwing-default-construction mantra.
The way that that deque is defined, it's impossible to conservatively
acquire the contents of one deque.
(When I say "conservative" in this context I mean it as an analogy to
"energy conservative" in physics, i.e. no effort is expended. Swapping
and moving are conservative, copying is not.)
For example, IMHO move construction should never, ever throw. I think
this is an immutable ideal, just like the energy conservation principle
is in physics. We start with a number of objects. We end with the same
number of objects, just in a different place. There shouldn't be at any
point any "effort" expended on allocating extra memory, aside from the
"friction" of moving bits around. Making the default constructor throw
makes moving via swap (the only move available for C++03)
non-conservative and consequently wasteful and potentially incorrect (if
peer code expects moving cannot engender exceptions). It is reasonable
to think that an object could embed a deque inside and, without risking
an exception, "absorbs" another deque.
Heck, if deque's constructor attempts to allocate memory (and
consequently throw), you can't even use the swap trick to ensure you
have completely emptied a deque. So picture the irony: _emptying_ a
deque via the swap trick could _throw_. This is a correctness issue that
I guess simply rules the swap trick out as a method of emptying a
container in C++03.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]