2

Another question from reading "Accelerated C++" by Andrew Koenig and Barbara E. Moo, and I'm at the chapter about constructors (5.1), using the example as before.

They write

we'll want to define two constructors: the first constructor takes no arguments and creates an empty Student_info object; the second takes a reference to an input stream and initializes the object by reading a student record from that stream.

leading to the example of using Student_info::Student_info(istream& is) {read(is);} as the second constructor which

delegates the real work to the read function. [...] read immediately gives these variables new values.

The Student_info class is

class Student_info {
public:
    std::string name() const (return n;}
    bool valid() const {return !homework.empty();}
    std::istream& read(std::istream&);

    double grade() const;
private:
    std::string n;
    double midterm, final;
    std::vector<double> homework;
};

Since read is already defined as a function under the Student_info class, why is there a need to use a second constructor - isn't this double work? Why not just use the default constructor, and then the function since both are already defined?

Community
  • 1
  • 1
sccs
  • 1,123
  • 3
  • 14
  • 27

2 Answers2

5

It isn't double work on the contrary it simplifies the object initialization for the caller who instantiates the class

If you create the class with a single constructor every time you have to do

std::istream is = std::cin;
Student_info si();
si.read(is);
// si.foo();
// si.bar();
// si.baz();

Maybe some other operations can be added that can be done in constructor. So you don't have to write them again when you need to instantiate the class. If you create 10 instances you will have to write

( 10 -1 = ) 9 more lines and this isn't a good approach for OOP

Student_info::Student_info(istream& is) 
{
    read(is);
    //foo();
    //bar();
    //baz();
}

But when you define two constructors like above, you can use the class like

std::istream is = std::cin;
Student_info si(is);

One of the main goals of OOP is writing reusable and not self repeating code and another goal is seperation of concerns. In many cases person who instantiates the object doesn't need to know implementation details of the class.

On your example read function can be private when its called inside constructor. This reaches us another concept of OOP Encapsulation

Finally this isn't double work and its a good approach for software design

qwerty
  • 2,065
  • 2
  • 28
  • 39
1

My question is since read is already defined as a function under the Student_info class, why is there a need to use a second constructor - isn't this double work?

The second constructor takes an argument from the caller and passes it on to the read function. This allows you to directly instantiate a Student_info with an std::istream.

std::istream is = ....;
Student_info si(is); // internally calls read(is)

as opposed to

std::istream is = ....;
Student_info si;
si.read(is);     // how could we use is if this call isn't made? Now we have to read some docs...

Why not just use the default constructor, and then the function since both are already defined?

Because it is better to construct objects such that they are in a coherent, useful state, rather than construct them first and then initialize them. This means users of the objects do not have to worry about whether the thing can be used or must first be initialized. For example, this function takes a reference to a Student_info:

void foo(const Student_into& si)
{
  // we want to use si in a way that might require that it has been
  // initialized with an istream
  si.doSomethingInvolvingInputStream(); // Wait, what if the stream hasn't been read in? 
                                        // Now we need a means to check that!
}

Ideally, foo should not have to worry about the object has been "initialized" or made valid.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • So it saves the step of initializing all the variables to (their versions of zero) just before it gets written over? Is there any speed/power difference, or is it just for clarity? – sccs Mar 22 '13 at 07:22
  • 1
    @sccs the variables are initialized first anyway, because the function is called from the body of the constructor. At this stage, all data members have been constructed. So the real reason is the design aspect I mentioned in the last paragraph. It isn't just clarity: a type that has to be explicitly initialized after construction is pretty useless (although I come across them every day at work) – juanchopanza Mar 22 '13 at 07:23
  • I just saw that, thanks! Just one more - does there ever exist programs where no such second constructor (with arguments) exists? – sccs Mar 22 '13 at 07:24
  • @sccs you mean classes? Yes, kind of. Anything that doesn't need any external input at the construction phase. But bear in mind that the compiler generates a copy constructor for you, and that is a constructor that takes arguments. If you don't want that, you have to actively disable it (but that is another story). – juanchopanza Mar 22 '13 at 07:29
  • 3
    And a small addition: if your class is useless in uninitiated state, you should not define default constructor to clarify it better. – SpongeBobFan Mar 22 '13 at 07:52