Section 2 - Anonymous Objects

8B.2.1 Objects Without Names

glass building

We still want an easy way to create an array of Student objects.  We haven't finished defining the Student class yet, but already from what we know about the constructor, we need to supply three pieces of data for each object.  And we're trying to do something analogous to what we did with doubles and strings earlier:

double myArray[] = { 10.2, 56.9, -33, 12, 0, 2, 4.8, 199.9, 73, -91.2 };

string myArray[] =
{
"martin", "claudia", "sandra", "samuels",
"terry", "jack", "clark", "palmer", "abraham", "Mike"
};

But how can we do this when the base data type of the class we are using in the array is a class?  We can create anonymous objects in the initialization.  An anonymous object is one that has no variable or identifier associated with it.  How could such an object even exist?  Easy.  Look:

Student  myClass[] =
{
Student("smith","fred", 95),  Student("bauer","jack",123),
Student("jacobs","carrie", 195),  Student("renquist","abe",148),
Student("3ackson","trevor", 108),  Student("perry","fred",225),
Student("loceff","fred", 44),  Student("stollings","pamela",452),
Student("charters","rodney", 295),  Student("cassar","john",321)
};

Of course. We don't really need any reference identifiers since the array name and the index, as in myClass[3], are going to be used if we have to identify any of our array elements.   So this use of the constructor, in which there is no reference on the LHS of some assignment operator, is perfect.

Incidentally, we can also create anonymous objects if we want to make one just to send off to a method in an argument list, but don't need the object after the call. For example:

drawDot( 100, 40, Color(255, 255, 128) );

This fictitious method call invokes a hypothetical anonymous Color object which we create just to send to the drawDot() method.  Perhaps we are drawing a tan colored dot at location 100, 40 on the screen.

We can also use anonymous objects to throw together an object to return in a function:

Student makeGuestStudent()
{
return Student("Guest", "Student", 0);
}

Back to our main topic, though, the initialization of our Student array. We can now see why one of the students ended up with the name "zz-error".  Look closely at the data and also the constructor.

8B.2.2 A Support Class for Students - StudentArrayUtilities

We add another component to our design: a class designed to process arrays of Students. This will separate the management of individual students (a Student class responsibility) with that of handling arrays of students (our new StudentArrayUtilities class). This new class has no data, but does have some static methods. Let's look at the class prototype:

// class StudentArrayUtilities prototype -----------------------
class StudentArrayUtilities
{
public:
static void printArray(string title, Student data[], int arraySize);
static void arraySort(Student array[], int arraySize);

private:
static bool floatLargestToTop(Student data[], int top);
static void mySwap(Student &a, Student &b);
};

We see two public methods:

  1. printArray() - a method that takes an array of Student objects and sends it out to the screen, and
  2. arraySort() - a method that takes an array and rearranges it, leaving it in a sorted order.

For our demonstration, we will only do sorts on last name.

Disclosure. This class has too much user-interface assumption built-in, namely the printArray() is going to a console but maybe it is intended for some other style display. It would be better if it were a toString() method that the client then sent to its output however it wished. But, we'll leave that for an imagined refinement, since we are short on time and have lots of other topics to cover.

8B.2.3 The Client View of the Student and Utility Classes

Now let's look at the entire main() method to see how we are going to have to finish defining Student and StudentArrayUtilities:

int main()
{
Student  myClass[] =
{
Student("smith","fred", 95),
Student("bauer","jack",123),
Student("jacobs","carrie", 195),  Student("renquist","abe",148),
Student("3ackson","trevor", 108),  Student("perry","fred",225),
Student("loceff","fred", 44),  Student("stollings","pamela",452),
Student("charters","rodney", 295),  Student("cassar","john",321)
};

int arraySize = sizeof(myClass) / sizeof(myClass[0]);

StudentArrayUtilities::printArray("Before: ", myClass, arraySize);
StudentArrayUtilities::arraySort(myClass, arraySize);
StudentArrayUtilities::printArray("After: ", myClass, arraySize);
}

We're good with the array initialization.  Beyond that, there are only three method calls, just like the earlier examples.  However, what's interesting about these calls is that they have a class name in front of them.  What do you know about method calls dereferenced by class names? I'm waiting ... .

Correct.  Static class methods.

And why do we use static class methods, ever?  Two reasons, both applicable here:

  1. They involve data or objects of their own class or a related class, and
  2. they do not make sense to be called from any individual object.

Great.  This tracks.  Printing or sorting an array of Students is certainly something that should be done by the Student-related class like StudentArrayUtilities, since such a class would know all the details and pitfalls of the Student data that it supports.  At the same time, printing or sorting an array of Students means dealing with a whole group.  There is no one single Student object that would naturally be used to dereference this kind of method.  Nor do we need an object of StudentArrayUtilities. That's why we are going to declare these as static methods, not as instance methods.

This is so important, I can't say it loud enough.  You have to understand why we are going to use a static method, not an instance method, for these two operations.  Also, in these cases, we are passing an  array of Student objects as an argument to the methods , but in other situations we may not pass any objects of the class into the static methods.  It depends on what the static methods are designed to do.

Let's take a peek at another static method (which we have not seen yet), this one in the Student class itself:

bool Student::validString( string testStr )
{
if (testStr.length() > 0 && isalpha(testStr[0]))
return true;
return false;
}

Why is this method static? The reason is that it does not act directly on any Student member. The string it is testing for validity is not assocated with a particular member -- it is used by two members firstName and lastName (as we shall see). Its job is to check whether the String that is potentially being assigned to one of those two private members is legal for the class.

8B.2.4 The Full Student Class Definition

Here's another static method of the Student class. This one is going to be used by the utility class's arraySort() method; it establishes the basis for the sort: last name.

// could be an instance method and, if so, would take one parameter
int Student::compareTwoStudents( Student firstStud, Student secondStud )
{
int result;

// this particular version based on last name only (case insensitive)
result = firstStud.lastName.compare(secondStud.lastName);

return result;
}

The comment at the top indicates that it didn't have to be a static method, and if it were designed to be an instance method, instead, it would take only one parameter (why, and what parameter?). However, there is something balanced about seeing both objects to be compared as parameters.

Static class methods can take any kind of parameter that makes sense for what they do.  The only unusual and probably unlikely parameter they would take would be a single object of the class itself.  Does anyone want to tell me why that would probably be a bad or at least rare, situation?  Public forum, please.

So, we are ready to see the rest of our Student class. 

// class Student prototype -----------------------
class Student
{
private:
string lastName;
string firstName;
int totalPoints;

public:
static const string DEFAULT_NAME;
static const int DEFAULT_POINTS = 0;
static const int MAX_POINTS = 1000;

public:
Student( string lst = DEFAULT_NAME, string fst = DEFAULT_NAME,
long pts = DEFAULT_POINTS);

// accessors and mutators
string getLastName() { return lastName; }
string getFirstName() { return firstName; }
int getTotalPoints() { return totalPoints; }

bool setLastName(string last);
bool setFirstName(string first);
bool setPoints(int pts);

static int compareTwoStudents( Student firstStud, Student secondStud );
string toString();

private:
static bool validString( string testStr );
static bool validPoints( int testPoints );

};  // end of class Student prototype --------------

// static initializations that can't be done in-line
const string Student::DEFAULT_NAME = "zz-error";

// beginning of Student method definitions -------------

// constructor requires parameters - no default supplied
Student::Student( string last, string first, long points)
{
if ( !setLastName(last) )
lastName = DEFAULT_NAME;
if ( !setFirstName(first) )
firstName = DEFAULT_NAME;
if ( !setPoints(points) )
totalPoints = DEFAULT_POINTS;
}

bool Student::setLastName(string last)
{
if ( !validString(last) )
return false;
lastName = last;
return true;
}

bool Student::setFirstName(string first)
{
if ( !validString(first) )
return false;
firstName = first;
return true;
}

bool Student::setPoints(int pts)
{
if ( !validPoints(pts) )
return false;
totalPoints = pts;
return true;
}

// could be an instance method and, if so, would take one parameter
int Student::compareTwoStudents( Student firstStud, Student secondStud )
{
int result;

// this particular version based on last name only (case insensitive)
result = firstStud.lastName.compare(secondStud.lastName);

return result;
}

string Student::toString()
{
string resultString;
ostringstream cnvrtFirst, cnvrtLast, cnvrtPoints;

cnvrtFirst << firstName;
cnvrtLast << lastName;
cnvrtPoints << totalPoints;

resultString = " "+ cnvrtLast.str()
+ ", " + cnvrtFirst.str()
+ " points: " + cnvrtPoints.str()
+ "\n";
return resultString;
}

bool Student::validString( string testStr )
{
if (testStr.length() > 0 && isalpha(testStr[0]))
return true;
return false;
}

bool Student::validPoints( int testPoints )
{
if (testPoints >= 0 && testPoints <= MAX_POINTS)
return true;
return false;
}
// end of Student method definitions  --------------

Study this class first, before going on.

8B.2.5 The Full StudentArrayUtilities Class Definition

// class StudentArrayUtilities prototype -----------------------
class StudentArrayUtilities
{
public:
static void printArray(string title, Student data[], int arraySize);
static void arraySort(Student array[], int arraySize);

private:
static bool floatLargestToTop(Student data[], int top);
static void mySwap(Student &a, Student &b);
};

// beginning of Student method definitions -------------

// print the array with string as a title for the message box
// this is somewhat controversial - we may or may not want an I/O
// methods in this class.  we'll accept it today
void StudentArrayUtilities::printArray(string title, Student data[], int arraySize)
{
string output = "";

cout << title << endl;

// build the output string from the individual Students:
for (int k = 0; k < arraySize; k++)
output += " "+ data[k].toString();

cout << output << endl;
}

void StudentArrayUtilities::arraySort(Student array[], int arraySize)
{
for (int k = 0; k < arraySize; k++)
// compare with method def to see where inner loop stops
if (!floatLargestToTop(array, arraySize-1-k))
return;
}

// returns true if a modification was made to the array
bool StudentArrayUtilities::floatLargestToTop(Student data[], int top)
{
bool changed = false;

// compare with client call to see where the loop stops
for (int k =0; k < top; k++)
if (  Student::compareTwoStudents(data[k], data[k+1]) > 0 )
{
mySwap(data[k], data[k+1]);
changed = true;
}
return changed;
}

void StudentArrayUtilities::mySwap(Student &a, Student &b)
{
Student temp("", "", 0);

temp = a;
a = b;
b = temp;
}

// end of StudentArrayUtilities method definitions  --------------

/* ------------------------------ run ----------------------------

Before:
smith, fred points: 95
bauer, jack points: 123
jacobs, carrie points: 195
renquist, abe points: 148
zz-error, trevor points: 108
perry, fred points: 225
loceff, fred points: 44
stollings, pamela points: 452
charters, rodney points: 295
cassar, john points: 321

After:
bauer, jack points: 123
cassar, john points: 321
charters, rodney points: 295
jacobs, carrie points: 195
loceff, fred points: 44
perry, fred points: 225
renquist, abe points: 148
smith, fred points: 95
stollings, pamela points: 452
zz-error, trevor points: 108

Press any key to continue . . .

---------------------------------------------------------------- */