Re: Creating an object that is read from an input stream.

From: David White (no_at_email.provided)
Date: 12/12/03


Date: Fri, 12 Dec 2003 20:22:33 +1100


"Jason Heyes" <geoffhys@optusnet.com.au> wrote in message
news:3fd93bd1$0$1024$afc38c87@news.optusnet.com.au...
> "Cy Edmunds" <cedmunds@spamless.rochester.rr.com> wrote in message
> news:%W1Cb.7559$UY6.701@twister.nyroc.rr.com...
> > This is what I meant in a previous post about not mixing up different
> > concepts. What does a Box have to do with streams? Nothing. Consider:
> >
> > #include <iostream>
> > #include <fstream>
> >
> > class Box
> > {
> > private:
> > int m_left, m_right, m_up, m_down;
> >
> > public:
> > Box() {}
> >
> > Box(int i_left, int i_right, int i_up, int i_down) :
> > m_left(i_left), m_right(i_right), m_up(i_up), m_down(i_down) {}
> >
> > int left() const {return m_left;}
> >
> > int right() const {return m_right;}
> >
> > int up() const {return m_up;}
> >
> > int down() const {return m_down;}
> > };
> >
> > std::istream &
> > operator >> (std::istream &istr, Box &b)
> > {
> > int i_left, i_right, i_up, i_down;
> > istr >> i_left >> i_right >> i_up >> i_down;
> > b = Box(i_left, i_right, i_up, i_down);
> > return istr;
> > }
> >
> > int main()
> > {
> > Box b;
> > std::ifstream ifs("box_numbers.txt");
> > ifs >> b;
> > if (!ifs)
> > {
> > std::cout << "oops\n";
> > return 1;
> > }
> > std::cout << b.left() << ' ' << b.right() << ' '
> > << b.up() << ' ' << b.down() << '\n';
> > }
> >
> > As you can see, in C++ it is easy to separate I/O streams from the objects
> > they work on. This is basically how std::complex works, for instance,
> > although in that case the class was templated and wide character streams
> had
> > to be implemented. Note that the error handling here is just the same as
> it
> > might be with any other stream operation -- the fact that it happens to be
> a
> > Box shouldn't require special treatment.
> >
> > Cy
>
> The Box class has a default constructor that leaves it's members undefined.
> Therefore the Box object created in main is not valid until
>
> ifs >> b;
>
> occurs. If anyone uses the Box object before that line, it will lead to
> unexpected results.

Well, the default constructor can put it into a valid state then.

> I don't see the point of the other constructor.

It's there so you can construct a box with whatever values you want. Is that not useful?

> Why not write:
>
> class Box
> {
> int left, right, up, down;
> public:
> Box() { }
> friend std::istream &operator>>(std::istream &is, Box &box);
> };
>
> std::istream &operator>>(std::istream &is, Box &box)
> {
> return is >> box.left >> box.right >> box.up >> box.down;
> }
>
> This way you don't need the other constructor.

Having it means that you don't even have to make the function a friend, so there is not a single
mention of std::istream cluttering up the Box class. In any case, surely such a constructor is
virtually mandatory for this class, just for general-purpose use without streams.

> You could also write:
>
> class Box
> {
> int left, right, up, down;
> public:
> Box() { }
>
> std::istream &extract(std::istream &is)
> {
> return is >> left >> right >> up >> down;
> }
> };
>
> std::istream &operator>>(std::istream &is, Box &box)
> { return box.extract(is); }
>
>
> The real problem as I see it is to get around the default constructor. You
> can make the default constructor private but that still allows invalid
> objects to exist.

By 'invalid' do you mean uninitialized, or not initialized with the right values (if the default
constructor were to initialize the members to default values)? And why is an "invalid" object a
problem? You are going to make it "valid" in the very next statement after you create it. For
example, is there anything disastrous about either of these?

int value;
is >> value;

Or:

int value = 0;
is >> value;

So what if the object you are reading into isn't the right value for a moment?

> Here is what I was thinking:
>
>
> class Box
> {
> int left, right, up, down;
>
> Box(int left_, int right_, int up_, int down_) :
> left(left_), right(right_), up(up_), down(down_)
> { }
>
> public:
> int get_area() const { return (right - left) * (up - down); }
>
> // reads a new box
> static Box *create(std::istream &is);
>
> std::istream &extract(std::istream &is)
> {
> int left, right, up, down;
> if (!(is >> left >> right >> up >> down))
> return is; // this box is valid even when input fails
> *this = Bar(left, right, up, down);
> return is;
> }
>
> std::ostream &insert(std::ostream &os) const
> { return os << left << right << up << down; }
> };
>
> // reads a new box
> Box *Box::create(std::istream &is)
> {
> int left, right, up, down;
> if (!(is >> left >> right >> up >> down))
> return 0;
> return new Box(left, right, up, down);
> }
>
> // reads into an old box
> std::istream &operator>>(std::istream &is, Box &box)
> { return box.extract(is); }

As I said in my first post (if I remember correctly back that far), why not ditch the extract
function in the class and make this function a friend so it can set the members directly? That
way you don't clutter your Box class with stream stuff.

> std::ostream &operator<<(std::istream &os, const Box &box)
> { return box.insert(os); }
>
> class BoxRef
> {
> SharedPtr<Box> ptr;
>
> public:
> int get_area() const { return ptr->get_area(); }
>
> std::istream &extract(std::istream &is)
> {
> if (ptr) {
> ptr.make_unique();
> return is >> *ptr;
> }
>
> Box *new_ptr = Box::create(is);
> if (!new_ptr)
> return is;
>
> ptr = new_ptr;
> return is;
> }
>
> std::ostream &insert(std::ostream &os) const
> { return os << *ptr; }
> };
>
> std::istream &operator>>(std::istream &is, BoxRef &ref)
> { return ref.extract(is); }
>
> std::ostream &operator<<(std::ostream &os, const BoxRef &ref)
> { return ref.insert(os); }
>
> int main()
> {
> BoxRef mybox;
> if (!(std::cin >> mybox))
> return 1;
> std::cout << mybox.get_area() << std::endl;
> std::cout << mybox << std::endl;
> return 0;
> }
>
>
> At no point in time does an invalid Box object exist. Instead we allow a
> null reference to exist and then we read into it. What do you think?

I think it's extremely and unnecessarily complicated. Apart from the new Box option (which could
easily be added to Cy's version), what are the advantages of your version?

DW



Relevant Pages

  • Re: Problem with linker
    ... but to have actually written a default constructor. ... such as overloading on int and pointer types. ... conversion of 0 to CString requires a user-defined conversion, ... acceleration operator *(distance d, time_squared t2); ...
    (microsoft.public.vc.mfc)
  • FAQ Suggestions
    ... It can only be applied to reference type variables converting to ... The .NET runtime can't guarantee that parameterless constructors will be ... performing by not having to call constructor code. ... number like casting an int to short) are always explicit. ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Languages for embedded
    ... calling constructors of the structure's members. ... If I want to know whether xyz had a constructor, ... On the other hand, if I have a C struct xyz, and the API provides a ... If function ftakes an int by value, it can't modify any int value ...
    (comp.arch.embedded)
  • Re: How to escape the Ocamls superfluous parentheses and type declarations?
    ... > The constructor Mycons expects 2 argument, ... constructor applied to zero or more arguments. ... >> Because you haven't declared the union of char and int. ...
    (comp.lang.ml)
  • Re: Creating an object that is read from an input stream.
    ... The Box class has a default constructor that leaves it's members undefined. ... int left, right, up, down; ... class BoxRef ... At no point in time does an invalid Box object exist. ...
    (comp.lang.cpp)