Emil Lang
Emil Lang

Reputation: 35

In C++, how do I use a private variable that is an array of objects?

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

Answers (4)

JaMiT
JaMiT

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.

  • Change the type of results.
  • Change the return type of GetResults().
  • Have 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

JaMiT
JaMiT

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

Emil Lang
Emil Lang

Reputation: 35

As pointed out by various people, there are various things to consider when tackling this issue.

  1. The getter for the object array can take an index number (good old int i) to target the element inside the array. e.g. R.GetResult(i)
  2. A declaration is not an expression! In my getter I used the expression "return results[MAX_RESULTS];" where MAX_RESULTS is a constant number... but this ends up meaning to return the array at the MAX_RESULTS-th element, clearly not intended.
  3. One thing for the future that I am going to try is to create a pointer function to the array member variable.
  4. It was suggested to quickly learn about vectors, since the declaration of "Result results[MAX_RESULTS];" is, to put it in the colorful words of a commentator, "an unholy mix of C++ and C that makes things more complicated than they need be further down the road."

Upvotes: 0

john
john

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

Related Questions