Re: designing classes without default c'tor; using them with STL
containers and operator>>(istream&)
* [rob desbois]:
I want a class whose invariant is that the contained data members are
valid - the constructor establishes this invariant, methods assume it
as their precondition and maintain it as their postcondition.
This requires the class to NOT have a default constructor.
I need to store instances of this class in STL containers, which
unfortunately requires that the class has a default constructor.
No, that isn't a general requirement.
The class must be copy-constructible and assignable in order to have
instances directly in STL containers.
Some /operations/ have an additional requirement of
default-constructible, e.g. std::vector::resize,
#include <string>
#include <vector>
struct Foo
{
std::string value;
Foo( char const s[] ): value( s ) {}
};
int main()
{
using namespace std;
vector<Foo> v; // OK
v.push_back( "blah blah" ); // OK
//v.resize( 52 ); // Req. default construction.
}
The
obvious solution here is that I have to implement a default
constructor which leaves the class in an invalid state, although this
unfortunately means that every method now has to check the invariant
as a precondition to ensure that it's not vulnerable to class users
who might use the default constructor and not read the documentation.
I don't find that obvious at all; in fact introducing a zombie state is
(usually, and here) a sure sign you're on the entirely wrong track.
Another requirement is that I need to implement (non-member) I/O
streaming functions. The output operator<<() is no problem, but again
with an input operator:
istream& operator>>(istream& in, const Foo& f);
I have to use this like so:
Foo f;
in >> f;
This obviously requires, again, that I can construct an instance
through the default constructor thus generating an invalid object.
Why do you have to use it like that?
E.g. why not
FooData data;
in >> fooData;
// Possibly rendundantly checking fooData here just to have control.
// Then
Foo const f( fooData );
More generally, think about moving that i/o and possibly serialization
stuff out of the class proper. Separate concerns. The above is just
one possibility.
Is there some great guideline for designing that everyone else knows
and I don't? Is this one of those 'pain-in-the-backside' things that
we just have to put up with and end up implementing default
constructors and invariant checks just so we can do the other things
we need to do?
No, and no.
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?