designing classes without default c'tor; using them with STLcontainers and operator>>(istream&)

R

[rob desbois]

Hi,

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. 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.

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.

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?

Thanks for any help anyone can offer...
--rob
 
J

Jim Langston

Hi,

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. 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.

One thing you could do is only store pointers to the class in standard
containers.
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.

Consider:
Foo f(in);

That is, a constructor that accepts an istream&
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?

There are work arounds as shown, and probably others.
 
R

[rob desbois]

I need to store instances of this class in STL containers, which
One thing you could do is only store pointers to the class in standard
containers.

Thank you - that's a good workaround that hadn't even occurred to me!
I'll look into it.
Consider:
Foo f(in);

That is, a constructor that accepts an istream&

Another good workaround that occurred to me about 5 minutes after my
initial post.
Seems a shame to give up the nice syntax afforded by the extraction
operator, but I guess I can't have my cake and eat it!

Cheers,
--rob
 
A

Alf P. Steinbach

* [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
 
R

[rob desbois]

I need to store instances of this class in STL containers, which
No, that isn't a general requirement.
Oh!

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,

Hmm...I could've sworn that I'd found a list of requirements and
default-constructability was one of them.
I'm using this class as the value_type of std::map, whose operator[]
implementation requires default construction.
It might be better to use insert() and make_pair() instead of a zombie
state.
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.

Thanks - that was my opinion!
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.

Ok, thank you. Separation of concerns is lacking in my current design;
I'll split things up a little more so that the class is responsible
for its contained data only and move I/O concerns elsewhere.
No, and no.

Damn, and good!
Cheers, & hth.,

Very much, thank you :)
--rob
 
J

jkherciueh

[rob desbois] wrote:
[snip]
Another good workaround that occurred to me about 5 minutes after my
initial post.
Seems a shame to give up the nice syntax afforded by the extraction
operator, but I guess I can't have my cake and eat it!

Huh? If you have a constructor from an istream, you could do:

istream& operator>> ( istream & istr, Foo & foo ) {
foo = Foo(istr);
return ( istr );
}

or

istream& operator>> ( istream & istr, Foo & foo ) {
Foo dummy ( istr );
swap( foo, dummy );
return ( istr );
}

or, in case you don't want extraction to throw, something like:

istream& operator>> ( istream & istr, Foo & foo ) {
try {
Foo dummy ( istr ); // could throw.
swap( foo, dummy ); // should not throw.
}
catch (...) {
// set whatever failure indicating bits you want in istr.
}
return ( istr );
}


Best

Kai-Uwe Bux
 
R

[rob desbois]

Huh? If you have a constructor from an istream, you could do:

istream& operator>> ( istream & istr, Foo & foo ) {
foo = Foo(istr);
return ( istr );
}

or

istream& operator>> ( istream & istr, Foo & foo ) {
Foo dummy ( istr );
swap( foo, dummy );
return ( istr );
}

or, in case you don't want extraction to throw, something like:

istream& operator>> ( istream & istr, Foo & foo ) {
try {
Foo dummy ( istr ); // could throw.
swap( foo, dummy ); // should not throw.
}
catch (...) {
// set whatever failure indicating bits you want in istr.
}
return ( istr );
}

I cannot - implementing operator>>(istream&, Foo&) requires that I
pass it a reference to an existing Foo object. I don't want Foo to be
a default-constructible class, so I'd have to pass it a valid object.
--rob
 
J

jkherciueh

I cannot - implementing operator>>(istream&, Foo&) requires that I
pass it a reference to an existing Foo object. I don't want Foo to be
a default-constructible class, so I'd have to pass it a valid object.

And what exactly would be the problem?

I think, you are confusing two unrelated issues. Of course you can implement
operator>>, and you could use it. E.g.:

Foo my_object ( some parameters );
... // do stuff with the object.
istr >> my_object; // read a new value into my_object
... // do more stuff

or

Foo my_object ( istr );
do {
...;
} while ( istr >> my_object );

Whether Foo is default constructible or not does no impact upon the
semantics of operator>>. But it might make it tricky to get a valid object
to begin with. Once you have one, you can use operator>> to change its
value.


On the other hand, why would it difficult for a default constructor to
create a valid object? Could it be that your class just lacks a "natural"
default value? In that case, you might consider picking a not so natural
default value. I never found that to be a problem.


Best

Kai-Uwe Bux
 
R

[rob desbois]

And what exactly would be the problem?

This line is the problem:
Foo f;
I cannot and do not want to implement a default constructor for my
class.
I think, you are confusing two unrelated issues. Of course you can implement
operator>>, and you could use it. E.g.:
operator>>() requires an *existing* object to operate on. I could use
it, but I'd have to manually construct a valid object purely to
satisfy the requirement.
Whether Foo is default constructible or not does no impact upon the
semantics of operator>>. But it might make it tricky to get a valid object
to begin with. Once you have one, you can use operator>> to change its
value.

Indeed - obtaining a valid object *is* the problem here.
On the other hand, why would it difficult for a default constructor to
create a valid object? Could it be that your class just lacks a "natural"
default value? In that case, you might consider picking a not so natural
default value. I never found that to be a problem.

There is no natural default value for this class which is my I *do not
want* a default constructor; it would make no sense for the class.
I wholeheartedly disagree with picking a not so natural default - that
seems extremely poor class design to me and is like my original
solution - degrading class design purely to satisfy a language
requirement.

--rob
 
J

jkherciueh

[rob desbois] wrote:
[snip]
There is no natural default value for this class which is my I *do not
want* a default constructor; it would make no sense for the class.
I wholeheartedly disagree with picking a not so natural default - that
seems extremely poor class design to me and is like my original
solution - degrading class design purely to satisfy a language
requirement.

I won't try to tell you what you should want or should not want. I feel the
need, however, to point out that picking a non-natural value is not
necessarily "extremely poor class design". It is what just about any bignum
class or std::complex does. There is nothing about the number 0 that makes
it "more natural" than 1. So why should std::complex default construct to
0? Well, because it solves problems.

On a related note: many classes do not have a natural comparison.
Nevertheless it does make sense to at least specialize std::less for those
classes that support _some_ ordering so that you can use them hassle-free
in associative containers. Restricting yourself to what feels natural can
get in the way of solving problems.


Best

Kai-Uwe Bux
 
J

Jim Langston

This line is the problem:
Foo f;
I cannot and do not want to implement a default constructor for my
class.

Not
Foo f;
but
Foo f(in);

Something like:

class Foo
{
Foo( istream& in )
{
// read from the stream in and construct the object in this
constructor
}
};

Have a *constructor* accept an input stream itself.
 
J

Jerry Coffin

Hi,

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.

That is incorrect. When you create a container instance, you can pass it
an object it'll copy as necessary. By default, that's an object created
with the default ctor, but yo can specify an object of your choice if
class doesn't have a default ctor:

struct X {
int y_;
X(int y) : y_(y) {}
};

// the object is the second parameter, so we also have to specify an
// initial size as the first parameter:
std::vector<X> xs(0, X(10));

[ ... ]
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.

This isn't really necessary either. You can construct your temporary
object specifying a parameter to use the non-default ctor, even though
that value is never used otherwise.

Foo f(1); // we'll assume 1 is a valid value:
in >> f;
 
R

[rob desbois]

I won't try to tell you what you should want or should not want. I feel the
need, however, to point out that picking a non-natural value is not
necessarily "extremely poor class design". It is what just about any bignum
class or std::complex does. There is nothing about the number 0 that makes
it "more natural" than 1. So why should std::complex default construct to
0? Well, because it solves problems.

Ok, you're probably right that is isn't 'extremely poor class design',
but the constructor for my actual class looks like this:
MeasurementSettings(const OutputInputMap& inputs, const
InputDivisorMap& divisors, const InputTrafficMap& trafficTypes);
Each of the parameters is a type of std::map.
The class's invariant places requirements on the contents of each of
those maps, and the minimum would be 2 entries in each of them.
Aside from anything else, a default value requires adding 6 entries to
3 different maps which adds noise to the class for no reason other
than to satisfy the requirements of a library class I wish to use.
IMHO the zombie method works a little better for my situation.
On a related note: many classes do not have a natural comparison.
Nevertheless it does make sense to at least specialize std::less for those
classes that support _some_ ordering so that you can use them hassle-free
in associative containers. Restricting yourself to what feels natural can
get in the way of solving problems.
My ultimate aim when developing any code to be used by other people is
to aim for the most natural looking solution.
It almost pains me to add support for ordering and default
construction then have to document that they "shouldn't" be used.

--rob
 
R

[rob desbois]

Not
Foo f;
but
Foo f(in);

Thanks - sorry that part was in reply to Kai-Uwe on the necessity of
adding a default constructor for std::map<>, not your suggestion of a
constructor taking an istream&.
--rob
 
R

[rob desbois]

That is incorrect. When you create a container instance, you can pass it
an object it'll copy as necessary. By default, that's an object created
with the default ctor, but yo can specify an object of your choice if
class doesn't have a default ctor:

Jerry - that's only relevant for std::vector<> (and perhaps other
sequence containers).
This isn't really necessary either. You can construct your temporary
object specifying a parameter to use the non-default ctor, even though
that value is never used otherwise.

Foo f(1); // we'll assume 1 is a valid value:
in >> f;

Correct, but again (see my last response to Kai-Uwe) my class's
construction is not as simple as passing an int, but involves
constructing 3 std::map<> instances and adding 2 entries to each. This
is why I want to avoid creating a non-natural default - partially
because I'm not a major fan of it as a general solution, but partially
because in this situation it's 9 lines of code to create that default
value.


Everyone's replies here have been very useful and cleared up in my
mind what is possible, and given me alternative ideas for solutions to
what I thought was a problem.

FYI I opted to create a default constructor for the class which
creates it in a zombie state. Any methods (other than e.g. copy
construct/assign) performed on the zombie will result in an
appropriate exception being thrown. This allows me to use std::map<>
without creating a true default that I don't feel is appropriate to
this situation.
For input streaming I have a constructor taking an istream& as
suggested. I quite like the syntax as it has a 'natural' feel to it
and fits in well with the language.

Thanks all for your time and advice :)
--rob
 
R

[rob desbois]

For input streaming I have a constructor taking an istream& as
suggested. I quite like the syntax as it has a 'natural' feel to it
and fits in well with the language.

Actually I tell a complete lie! I followed Alf's advice:
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.

I now have a non-member function to read the constructor parameters
from an istream& and construct the object from that.
Not quite true serialization, but it separates the concerns nicely for
me.

--rob
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top