Assignment: C++ Smoke and Mirrors


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] = ''; }

    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