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.
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();
}