Assignment 3
Home ] Up ] Assignment 1 ] Assignment 2 ] [ Assignment 3 ] Assignment 4 ] Assignment 5 ] Assignment 6 ] Assignment 7 ] Old Assignments ]

 

C++ 'Smoke and Mirrors'

The Nothing class

Here's a 'useful' class:

#ifndef __NOTHING_H
#define __NOTHING_H
//
// nothing.h
//

class Nothing
{
};

#endif

and here is a similarly useful program that uses it:

//
// nothing.cpp
//

#include "nothing.h"

int main()
{
    const Nothing ness;	// Some compilers don't like this
    //const Nothing ness = Nothing();	// so try this instead

    Nothing doing(ness);	// If your compiler doesn't like this
    //Nothing doing = ness;	// try this instead

    doing = ness;

    Nothing *here = &doing;
    const Nothing *anywhere = &ness;

    return 0;
}

Convince yourself that this program compiles without error, and executes without failure.

Explain why. What did the C++ compiler do for you?

Write a Nothing class that does exactly the same thing as the (empty) one above, but where you supply all the equivalent member functions explicitly. Just change nothing.h, not nothing.cpp.

Compile and link the nothing.cpp file against your version of the Nothing class, and show that your member functions get called at the right places.

An Expedition into Efficiency

Consider the following simple MyString class:

#ifndef __MYSTRING_H
#define __MYSTRING_H
//
// mystring.h (Not to be confused with string.h!)
//
// A minimal string class
//

#include <string.h> // for strlen(), strcpy()
#include <iostream.h> // for ostream stuff

class MyString
{
  public:
    MyString()
      : m_len(0), m_ptr(new char[1])
    { m_ptr[0] = '\0'; }

    MyString(const char *str)
      : m_len(strlen(str))
    {
        m_ptr = new char[m_len+1];
        strcpy(m_ptr, str);
    }

    MyString(const MyString &src)
      : m_len(src.m_len), m_ptr(new char[src.m_len+1])
    { strcpy(m_ptr, src.m_ptr); }

    ~MyString()
    { delete [] m_ptr; /* Clean up */ }

    MyString &operator=(const MyString &src)
    {
        if (this != &src)	// Prevent assignment to self, causing corruption
        {
            delete [] m_ptr;	// Clean up previously allocated space first
            m_len = src.m_len;
            m_ptr = new char[m_len+1];
            strcpy(m_ptr, src.m_ptr);
        }
        return *this;
    }

    friend ostream &operator<<(ostream &o, const MyString &src)
    { return o << src.m_ptr; }

  private:
    unsigned int  m_len;  // length of string, in chars
    char         *m_ptr;  // pointer to allocated array of chars
};

#endif

Now consider the simple Person class:

#ifndef __PERSON_H
#define __PERSON_H
//
// person.h
//
//	A very simple Person class.
//

#include <iostream.h>
#include "mystring.h"

class Person
{
  public:
    Person(const char *n, const char *adr, int ag)
      : m_name(n), m_address(adr), m_age(ag)
    {}

    virtual ~Person() {}

    int IsOlder(const Person &other)
    { return m_age > other.m_age; }

    void Print()
    {
        cout << "Name: "    << m_name    << endl
             << "Address: " << m_address << endl
             << "Age: "     << m_age     << endl;
    }

  private:
    MyString m_name;     // Person's name
    MyString m_address;  // Person's address
    int      m_age;      // Person's age
};

#endif

and the simple Student class:

#ifndef __STUDENT_H
#define __STUDENT_H
//
// student.h
//
//	A very simple Student class.
//

#include <iostream.h>
#include "person.h"
#include "mystring.h"

class Student : public Person
{
  public:
    Student(const char *nam, const char *adr, int ag,
            const char *sch, const char *schadr)
      : Person(nam, adr, ag),
        m_schoolName(sch), m_schoolAddress(schadr)
    {}

    virtual ~Student() {}

    void Print()
    {
        Person::Print();
        cout << "Attending: " << m_schoolName << endl
             << " " << m_schoolAddress << endl;
    }

  private:
    MyString m_schoolName;
    MyString m_schoolAddress;
};

#endif

Finally, consider the program:

//
// student.cpp
//

#include <iostream.h>
#include "student.h"

static Student getOlderStudent(Student s1, Student s2)
{
    return s1.IsOlder(s2) ? s1 : s2;
}

int main()
{
    Student s1("Fred Bloggs", "56 Main St., Anytown, USA", 23,
               "Yale", "New Haven, CT");
    Student s2("Mary Jones", "12 Canyon Court, Prime Real Estate, USA", 19,
               "Harvard", "Cambridge, MA");

    Student s3 = getOlderStudent(s1, s2);

    cout << "The Older student is:" << endl;
    s3.Print();
    return 0;
}

In this program:

  1. How many times (total), when and why, are the Student constructors (which ones?) and destructor invoked?
  2. How many times (total), when and why, are the Person constructors (which ones?) and destructor invoked?
  3. How many times (total), when and why, are each of the MyString constructors (which ones?) and destructor invoked?
  4. How can you improve these figures? (You should be able to improve these numbers significantly!)

Hint: From the start of the program to its end, what would you expect the relationship to be between the number of calls to a class constructor and the number of calls to the same class's destructor?

Modify the program in student.cpp (not the classes!) to significantly reduce the number of member functions that are invoked, while still producing correct results.

Do not add to or remove any of the arguments in the getOlderStudent() function!

 
This page was last changed on 30 Apr 2006