Reputation: 35
I'm trying to learn C++ and figuring out how to access a private member variable that is an array of objects. My objective is trying to print out the data that's in the array of objects. Suppose that my header looks like this.
using namespace std;
const unsigned MAX_RESULTS = 10;
class StudentRecords{
public:
StudentRecords();
//bunch of other getters and setters here
Result GetResults() const; //my lame attempt at trying to access the obj-array private var
private:
Result results[MAX_RESULTS]; // array of Result of MAX_RESULTS number of elements
//+other private variables
};
ostream& operator <<( ostream& os, const StudentRecords& R);
In the above, there is supposed to be a private array of Result objects called results, which has a size of MAX_RESULTS, which is supposed to be 10 here. Now, using my overloaded operator << the idea is to print the contents of Result to 'file' so as to speak. Since it's an array, I want to print out all the results in the array using a for loop.
Result StudentRecords::GetResults() const
{
return results[MAX_RESULTS];
}
ostream & operator <<( ostream & os, const StudentRecords& R )
{
for(unsigned i = 0; i < SomeNumber; i++)
{
os << R.GetResults()[i] << '\n'; //this won't work of course see error
}
return os;
}
There will be an error stating:
error: no match for 'operator[]' (operand types are 'Result' and 'unsigned int')|
I already overloaded the << operator in my Result class in order to print out the values in that class. The problem is that I don't know how to iterate through the results array. From what I've googled I understand that you can use some kind of pointer function for example here: C++: Setters and Getters for Arrays
When I try to write the function like this:
Result* GetResults() const;
I will get an error stating:
error: cannot convert 'const Result' to 'Result*' in return|
Leaving out the * allows the code to compile, but predictably I get a bunch of garbage values from my array. So assuming that my class has an array of objects, and those objects have their own variables, how do I print out the values from my array of objects? I appreciate the help.
Upvotes: 2
Views: 1065
Reputation: 17023
Another approach (not always a good approach, but something to keep in mind) is to keep the idea of returning the array, but upgrade from C-style arrays to std::array
. A std::array
behaves a lot like a C-style array, but it does not decay to a pointer. This makes it easier to pass the array to and from functions.
// In the class definition
const std::array<Result, MAX_RESULTS> & GetResults() const {
return results;
}
private:
std::array<Result, MAX_RESULTS> results;
Returning a reference prevents unnecessary copies, and making the reference const
prevents outside code from changing your data.
With this type of returned value, the expression R.GetResults()[i]
becomes valid in your streaming operator. In fact, there might be only three changes required in your code to use a std::array
, and those changes are all demonstrated in the above code block.
results
.GetResults()
.GetResults()
return simply results
.(While I also inlined the definition of GetResults()
, that was just for my convenience, not part of the fix.)
However, we can still do better. The name MAX_RESULTS
suggests that there could be fewer usable results in the array than the maximum. This inference is supported by the use of SomeNumber
instead of MAX_RESULTS
in operator<<
. For an array whose useful size is not known at compile time, a std::vector
is a more suitable solution.
Unlike switching to std::array
, a switch to std::vector
will require changes to some of the code that was not included as part of the question. For example, there is probably a method for adding a result. Instead of tracking how many results have been stored, assigning the new result to the next available spot, and incrementing the count, the new approach would be to push_back()
the new result. Once everything is converted, the MAX_RESULTS
constant should be unused hence can be eliminated (this is your first benefit of switching to vectors).
For the code that is in the question, the changes are similar to what was done when switching to std::array
.
// In the class definition
const std::vector<Result> & GetResults() const {
return results;
}
private:
std::vector<Result> results;
As with the std::array
approach, your operator<<
will work as you wrote it, assuming SomeNumber
is somehow set to R.GetResults().size()
. You could, though, take the implementation one step further by switching to a range-based loop.
ostream & operator <<( ostream & os, const StudentRecords& R )
{
// Range-based looping removes the need to track indices.
for (const auto& result : R.GetResults())
{
os << result << '\n';
}
return os;
}
The downside of these approaches is that "array" or "vector" becomes part of the interface rather than an implementation detail. It is a judgment call as to which design is preferable. Personally, if returning a vector is desirable, I would be inclined to put something like
// Container used to store `Result` objects.
using ResultContainer = std::vector<Result>;
in the class definition, then replace all other uses of std::vector<Result>
with ResultContainer
. This tells users of your class that they may assume ResultContainer
is a container and attempt range-based looping over it, but implies that vector-specific functionality should not be assumed. (Implications are weak; better documentation would be better.) This gives you the flexibility to replace std::vector
with a different container, if there is ever a reason to do so.
Upvotes: 0
Reputation: 17023
One approach is to make the output function part of the class so that it can access the results
array directly. While operator<<
cannot be literally part of your class, you can make it logically part of the class by declaring it a friend.
class StudentRecords {
friend ostream& operator <<( ost& os, const StudentRecords& R);
// Rest of the class definition
};
ostream & operator <<(ostream & os, const StudentRecords& R)
{
for(unsigned i = 0; i < MAX_RESULTS; i++) {
os << R.results[i] << '\n';
}
return os;
}
Alternatively, you could avoid friendship by defining a member function that prints the array and having operator<<
call that member.
class StudentRecords {
public:
void print_to(std::ostream& os) const
{
for(unsigned i = 0; i < MAX_RESULTS; i++) {
os << results[i] << '\n';
}
}
// Rest of the class definition
};
ostream & operator <<(ostream & os, const StudentRecords& R)
{
R.print_to(os);
return os;
}
Note that this approach is geared toward the case where nothing else outside the class will need to access the results
array. If you need external access to the array, you might as well fix your getter (as in other answers) and use that public access for printing.
Upvotes: 1
Reputation: 35
As pointed out by various people, there are various things to consider when tackling this issue.
Upvotes: 0
Reputation: 87997
Result StudentRecords::GetResults() const
{
return results[MAX_RESULTS];
}
This is a common newbie misunderstanding. Because you declared the array as results[MAX_RESULTS]
you think that results[MAX_RESULTS]
somehow means the whole array. But it doesn't, when you are using an array []
is used to access individual elements of the array. And of course there is no element at index MAX_RESULTS
, that's past the end of the array.
Bottom line is that the syntax for declarations and the syntax for expressions is not the same.
You could do something like this
const Result* StudentRecords::GetResults() const
{
return results;
}
This returns a pointer to the array, not the array itself (which is actually impossible to do in C++). But that should be close enough for now.
Upvotes: 4