Search code examples
c++default-constructor

How do I use a stream extraction operator if a default constructor isn't provided?


I have a software engineering question in relation to C++, constructors (default and otherwise), the stream extraction operator, and reference member variables. I know that is a lot of topics, but all of them seem to depend on each other in this problem.

For a problem that I am trying to solve, I wanted to make a Vec class which holds the data for the problem, and a ProblemSolver class which will eventually have methods to solve the problem. The ProblemSolver class doesn't make sense without the data, so I wrote it with a copy of the Vec data (considerations for reference member occurred later...). So here was what my first version looked like:

#include <iostream>
#include <vector>

class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
};

class ProblemSolver {
private:
    Vec data;
public:
    ProblemSolver(Vec d) : data(d) { }
};

int main() {
    int m;
    std::cin >> m;
    Vec v(m);
    ProblemSolver ps(v);
}

So far this seemed okay. Then I though that I should provide stream extraction operators for each class so that the objects could be in charge of reading in its own data instead of reading it in main. So I rewrote the classes like this:

#include <iostream>
#include <vector>

class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
    friend std::istream& operator >> ( std::istream& input, Vec &V ) {
        int m;
        input >> m;
        V.v = std::vector<int>(m, 0);
        return input;
    }
};

class ProblemSolver {
private:
    Vec data;
public:
    ProblemSolver(Vec d) : data(d) { }
    friend std::istream& operator >> ( std::istream& input, ProblemSolver &P ) {
        input >> P.data;
        return input;
    }
};

int main() {
    ProblemSolver p;
    std::cin >> p;
}

This is where I ran into some confusion because the first line of main() is trying to call the default constructor for ProblemSolver. I understand that it's an issue because one isn't provided, but what should I really do about this in terms of good practice for my problem above? Should I always provide a default constructor, even though the class doesn't make sense without an actual set of data? Was I wrong that I should just let the object handle its own data reading, and that I shouldn't write the stream extraction operators?

Because of this article, I considered using a reference member variable instead because the "data for the problem solver class still exists outside of the existence of the instances of the problem solver". So I again rewrote it like this:

#include <iostream>
#include <vector>

class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
    friend std::istream& operator >> ( std::istream& input, Vec &V ) {
        int m;
        std::cin >> m;
        V.v = std::vector<int>(m, 0);
        return input;
    }
};

class ProblemSolver {
private:
    const Vec& v;
public:
    ProblemSolver(const Vec& v_) : v(v_) { }
};

int main() {
    int m;
    std::cin >> m;
    Vec v(m);
    ProblemSolver p(v);
}

But with this, I still can't write a stream extraction operator for ProblemSolver and the one written for Vec is practically useless because I'd have to instantiate a Vec object before I can use the stream extraction operator. Therefore in the example above I still have to read the int m data in main() and I can't do something like Vec v; std::cin >> v; ProblemSolver p(v); for the same reasons as in the second code segment. I think it should be possible to do something like instantiating and reading in a variable all at once like std::cin >> ProblemSolver p;, but obviously that isn't the case.

So my primary question is how do I properly write these two classes? Should I include default constructors? My understanding is that I shouldn't provide a default constructor if the class doesn't make sense without any data, but is that wrong? If I don't write a default constructor, should I instantiate a class with useless data provided to the standard constructor just so that I can then use the stream extraction operator on it? That seems like a bad practice to instantiate an object falsely before stream extraction.

To be clear, I'm not just looking for just a solution that works. I'd like to develop actual good practice and techniques for situations like these, so please support your answer with justification.


Solution

  • In this case, the object must be constructed first.

    Some options are:

    • have a default costructor which construts with a default value (maybe 0).

    • have a constructor which takes reference to istream which will then use it to load values.

    so something like:

    class Vec {
    private:
        std::vector<int> v;
    public:
        Vec(const int s) : v(s, 0) { }
        Vec(std::istream& input) 
        {
            int m;
            input >> m;
            v = std::vector<int>(m, 0);
        }
        friend std::istream& operator >> ( std::istream& input, Vec &V ) {
            int m;
            input >> m;
            V.v = std::vector<int>(m, 0);
            return input;
        }
    };
    
    
    class ProblemSolver {
    private:
        Vec v;
    public:
        ProblemSolver(Vec v_) : v(v_) { }
        ProblemSolver() : v(0) { }
        ProblemSolver(std::istream& in) : v(in) { } 
    };
    

    If you are adamant that the class will not make sense without a value, you will be better of writing a generator function and abandoning the stream extraction operator. like so:

    
    class Vec {
    private:
        std::vector<int> v;
    public:
        Vec(const int s) : v(s, 0) { }
    };
    
    class ProblemSolver {
    private:
        Vec data;
    public:
        ProblemSolver(Vec d) : data(d) { }
        static ProblemSolver generate()
        {
            int m;
            std::cin >> m;
            return ProblemSolver{Vec{m}};
        }
    };
    
    int main() {
        ProblemSolver p = ProblemSolver::generate();
    }