CSC309 Oct01

slide version

single file version

Contents

  1. C++ Class with No Pointer Members
  2. C++ Class with POinter Members
  3. Classes with Pointer Members and Copying Values
  4. Default Copying
  5. Copy Constructor
  6. Assignment operator
  7. Copying Steps
  8. Destructor
  9. Example: Default Copying
  10. The Default Copy Constructor
  11. Default Assignment
  12. The Default Assignment Operator
  13. Example: Class with a Pointer Member
  14. Consequences!
  15. Non-Default Copy Constructor
  16. The Non-Default Assignment Operator
  17. Types and Subtypes
  18. Inheritance and Subclasses
  19. Example
  20. Person Class
  21. Virtual Qualifier
  22. King Subclass
  23. Additional Data and/or Methods in Subclasses
  24. Accessing Base Class Members
  25. Constructors
  26. Order of Execution of Constructors/Destructors
  27. Compatibility of Types and Subtypes
  28. Casts
  29. Examples

C++ Class with No Pointer Members[1] [top]

Instances of a C++ class that contains pointer data members will typically have allocated heap storage and assigned addresses of that heap memory to those pointers.

A destructor is needed to release that extra heap memory when the instance itself is destroyed.

However, a C++ class that contains no pointer data members, typically only need contain the following:


      class class_name
      {
      private_data_members
      private_methods

      public:
      no_param_constructor

      accessor_methods

      mutator_methods

      };
    

Note: If subclasses of this class will later be created, a destructor should be added even though it doesn't do anything.

C++ Class with POinter Members[2] [top]

A C++ class that contains pointer data members will typically need to have 3 additional members to properly manage the extra heap storage:


      class class_name
      {
      private_data_members
      private_methods

      public:
      no_param_constructor
      copy_constructor


      accessor_methods

      mutator_methods

      assignment_operator

      destructor

      };
    

Classes with Pointer Members and Copying Values[3] [top]

Values of variables are copied in three different settings.

Default Copying[4] [top]

The default way that a class value is copied is just to copy of each of the corresponding members.

If a class has no pointer members, this is just what is needed to copy the entire instance.

If a class has one or more pointer members, the heap storage that the pointer "points to" is not copied by the default copy.

Only the pointer itself is copied by default memberwise copying; this is called shallow copying.

To copy the entire value, including copying the extra heap storage referenced by pointer members is called deep copying.

Deep copying requires that you write additional methods to replace the default copying.

Copy Constructor[5] [top]

The copy constructor is used implicitly by the compiler:

Assignment operator[6] [top]

The assignment operator, =, can be redefined (overloaded) for instances of classes.

If an assignment operator has been added to a class, the compiler will then use the overload resolution rules to use it instead of the default assignment for instances of the class.

Copying Steps[7] [top]

When writing either a copy constructor or an overloaded assignment operator, several steps are needed:

In both cases the value of some source class instance is to be copied to a target class instance.

Destructor[8] [top]

If a class has a pointer member, this pointer member points to heap storage that has been allocated by a constructor or other members of the class, even if no deep copy of the value has been made.

This heap storage will not be made free when the class instance is destroyed unless it is explicitly deleted.

The destructor should have statements to delete the storage.

Example: Default Copying[9] [top]

Three places where a class instance value is copied:

      void print(Employee e);
      ...
      Employee e1("Harry", "234-56-7890", 50000.00);
      Employee e2(e1);  // 1. e1 value copied to e2
      Employee e3;

      e3 = e1;          // 2. e1 value copied to e3

      print(e1);        // 3. e1 value copied to e,
                        //    the formal parameter of print
    

Here is the class. Why is there no copy constructor, assignment operator, or destructor?

    1	class Employee
    2	{
    3	  string name;
    4	  string ssn;
    5	  double salary;
    6	public:
    7	  Employee();
    8	  Employee(const string& nm, const string& ss, double sal);
    9	
   10	  string getName();
   11	  string getSSN();
   12	  double getSalary();
   13	
   14	  void setSalary(double sal);
   15	
   16	};








    

The Default Copy Constructor[10] [top]

Here is a copy constructor added to the Employee class.

This copy constructor is equivalent to the default copy constructor you get if one is not written.

    1   class Employee
    2   {
    3     string name;
    4     string ssn;
    5     double salary;
    6   public:
    7     Employee();
    8     Employee(const string& nm, const string& ss, double sal);
    9     Employee(const Employee& e)
   10     {
   11       name = e.name;
   12       ssn = e.ssn;
   13       salary = e.salary;
   14     }
   15       
   16     string getName();
   17     string getSSN();
   18     double getSalary();
   19   
   20     void setSalary(double sal);
   21   
   22   };








    

Default Assignment[11] [top]

Functions/methods can be overloaded in C++, but how do you overload the assignment operator?

The same way.

Operators have function names that can be used precisely for this purpose.

The name of the function that corresponds to the = operator is made up of the key word operator and the operator symbol:

      operator=
    

If we make it be a member function of the Employee class, then it can be called just as any other member function, with the member access operators "." or if we have a pointer to an Employee with the "->" member access through a pointer.

The = operator has 2 operands. As a member function:

      Employee e1("Harry", "234-56-7890", 5000.00);
      Employee e3;

      e3 = e1;
    

The assignment could also be written using the function notation for operator =

      e3.operator=(e1);   // Function notation for e3 = e1;
    

The Default Assignment Operator[12] [top]

Here is an assignment operator added to the Employee class.

This assignment operator is equivalent to the default assignment operator you get if one is not written.

    1   class Employee
    2   {
    3     string name;
    4     string ssn;
    5     double salary;
    6   public:
    7     Employee();
    8     Employee(const string& nm, const string& ss, double sal);
    9   
   10     void operator=(const Employee& e)
   11     {
   12       name = e.name;
   13       ssn = e.ssn;
   14       salary = e.salary;
   15     }
   16       
   17     string getName();
   18     string getSSN();
   19     double getSalary();
   20   
   21     void setSalary(double sal);
   22   
   23   };








    

Example: Class with a Pointer Member[13] [top]

Here is a different version of the Employee class that uses a C-string for the employee name instead of a c++ string type.

But note we haven't defined a copy constructor or an assignment operator as we should!

    1	class Employee
    2	{
    3	  char *name;
    4	  char ssn[12];
    5	  double salary;
    6	public:
    7	  Employee();
    8	  Employee(const char nm[], const char ss[], double sal)
    9	  {
   10	    int len = strlen(nm);
   11	    name = new char[len + 1];
   12	    strcpy(name, nm);
   13	    memset(ssn, '\0', 12);
   14	    strncpy(ssn, ss, 11);
   15	    salary = sal;
   16	
   17	  }
   18	  string getName();
   19	  string getSSN();
   20	  double getSalary();
   21	
   22	  void setSalary(double sal);
   23	
   24	  ~Employee()
   25	  {
   26	    delete [] name;
   27	  }
   28	
   29	};








    

Consequences![14] [top]

Suppose we write a function to print an Employee:

    1   void print(Employee e)
    2   {
    3     cout.setf(ios::fixed);
    4     cout.precision(2);
    5     cout << "Name: " << e.getName() << endl;
    6     cout << "SSN: " << e.getSSN() << endl;
    7     cout << "Salary: $" << e.getSalary() << enld;
    8   }
    9
   10   int main()
   11   {
   12      Employee e1("Harry", "234-56-7890", 50000.00);
   13
   14      print(e1);
   15      //( problem here)...
   16   }

What's the problem?









    

Non-Default Copy Constructor[15] [top]

At lines 18 - 26 is a copy constructor for this version of Employee that makes a deep copy unlike the shallow copy made by the default copy constructor.

    1   class Employee
    2   {
    3     char *name;
    4     char ssn[12];
    5     double salary;
    6   public:
    7     Employee();
    8     Employee(const char nm[], const char ss[], double sal)
    9     {
   10       int len = strlen(nm);
   11       name = new char[len + 1];
   12       strcpy(name, nm);
   13       memset(ssn, '\0', 12);
   14       strncpy(ssn, ss, 11);
   15       salary = sal;
   16   
   17     }
   18     Employee(const Employee& e1)
   19     {
   20       int len = strlen(e1.name);
   21       name = new char[len + 1];
   22       strcpy(name, e1.name);
   23       memset(ssn, '\0', 12);
   24       strncpy(ssn, e1.ssn, 11);
   25       salary = e1.salary;
   26     }
   27           
   28     string getName();
   29     string getSSN();
   30     double getSalary();
   31   
   32     void setSalary(double sal);
   33   
   34     ~Employee()
   35     {
   36       delete [] name;
   37     }
   38   
   39   };








    

The Non-Default Assignment Operator[16] [top]

Lines 19 - 28 show the assignment operator. Note line 21 is the only difference between the copy constructor and the assignment operator.

    1   class Employee
    2   {
    3     char *name;
    4     char ssn[12];
    5     double salary;
    6   public:
    7     Employee();
    8     Employee(const char nm[], const char ss[], double sal);
    9     Employee(const Employee& e1)
   10     {
   11       int len = strlen(e1.name);
   12       name = new char[len + 1];
   13       strcpy(name, e1.name);
   14       memset(ssn, '\0', 12);
   15       strncpy(ssn, e1.ssn, 11);
   16       salary = e1.salary;
   17     }
   18       
   19     void operator=(const Employee& e1)
   20     {
   21       delete [] name;
   22       int len = strlen(e1.name);
   23       name = new char[len + 1];
   24       strcpy(name, e1.name);
   25       memset(ssn, '\0', 12);
   26       strncpy(ssn, e1.ssn, 11);
   27       salary = e1.salary;
   28     }
   29     string getName();
   30     string getSSN();
   31     double getSalary();
   32     void setSalary(double sal);
   33   
   34     ~Employee()
   35     {
   36       delete [] name;
   37     }
   38   
   39   };

For now, we'll avoid classes with pointer members and revisit the associated memory allocation and operator overloading in more detail later (see chapters 8 and 9).









    

Types and Subtypes[17] [top]

A subtype of a type defines a different type.

Example: Person, King

A subtype is a more specialized instance of the parent type.

Since a subtype instance is still an instance of the parent tyep, all operations valid for the type should also be valid for any instance of the subtype.

This means that in any code that expects an instance of the type, a more specialized instance of the subtype is acceptable.

Inheritance and Subclasses[18] [top]

Classes define types and subclasses can define their subtypes.

If B is a class, we can create a subclass, D using this syntax:

      class D : public B
      {



      };
    

Example[19] [top]

Consider a classification of members of a "kingdom":

All are instances of Person, but there are more specific subtypes:

		    Person
		      |
	  +----+------+------+------+
	  |    |      |      |      |
	King Queen  Peer  Knight  Peasant

    

King and Queen are at the same rank.

A Peer is the next rank below the King and Queen.

The Knight is below a Peer, but above the Peasant.

Person Class[20] [top]

A Person instance has a title and that Person's rank can be compared to another Person instance with the superiorTo memthod.

class Person
{

public:
  Person();
  virtual const char * title();
  virtual bool superiorTo(Person&);

  virtual ~Person();
  
};

Virtual Qualifier[21] [top]

Should all member functions be qualified with virtual?

Presumably the getSSN() method would not need to be redefined in any subclass of Employee, e.g., a Manager subclass.

In that case the virtual qualifier should be omitted because, there is a bit of run time overhead in calling virtual functions.

King Subclass[22] [top]

The King class will inherit from Person class.

Example:

    1	#ifndef KING_H
    2	#define KING_H
    3	#include "Person.h"
    4	
    5	class King : public Person
    6	{
    7	
    8	public:
    9	  King();
   10	  const char * title();
   11	  bool superiorTo(Person&);
   12	};
   13	#endif

Will there be subclasses of King?

Should the methods of King inherited from person be qualified with virtual?

No. Once a method is qualified as virtual in a class, it is virtual in all descendant classes whether the virtual keyword is present in the subclasses or not.

Additional Data and/or Methods in Subclasses[23] [top]

Unlike the King class, a subclass typically adds some additional data members and/or methods that are only applicable to its instances, but not to its parent:

Here is the Peer class:

    1	#ifndef PEER_H
    2	#define PEER_H
    3	#include "Person.h"
    4	
    5	class Peer : public Person
    6	{
    7	private:
    8	  Degree d;
    9	  static const int max_territory = 20;
   10	  static const int max_degree = 8;
   11	  static const int max_title = max_territory + max_degree + 4;
   12	  static const int max_succession = max_title + 9;
   13	  char territory[max_territory + 1];
   14	  char full_title[max_title + 1];
   15	  char title_with_succession[max_succession + 1];
   16	  int succession;
   17	
   18	public:
   19	  Peer();
   20	  Peer(const Degree& dval, const char ter[], int suc );
   21	  const char * title();
   22	  bool superiorTo(Person&);
   23	  const char * getSuccessionTitle();
   24	  int getSuccession();
   25	};
   26	#endif







Accessing Base Class Members[24] [top]

A subclass method can acces the public and protected members of its parent class.

A subclass method cannot directly access private members of its parent class.

The subclass instance still inherits the private members, it must go through some public or protected method to manipulate those members.

Constructors[25] [top]

A constructor for a subclass usually cannot access the private members of its parent class because they are usually private.

You also can't call the parent constructor from inside the subclass constructor.

The correct way is to initialize the members inherited from the parent is to include a "call" to the parent constructor in the constructor initializer list.

Order of Execution of Constructors/Destructors[26] [top]

When a subclass instance is created, the parent's constructor is executed first, then the subclass constructor.

When a subclass instance is destroyed (deallocated), the destructors execute in the reverse order.

That is, the subclass destructor executes first, then its parent's destructor.

Compatibility of Types and Subtypes[27] [top]

A subclass type is a "kind of" its parent's type.

So a subclass value can be assigned to a parent type variable.

Similarly, a pointer to a subclass instance can be assigned to a variable which is a pointer to the parent type.

These are called upcasts

Assigning a parent type value to a subclass type variable requires a cast.

Assigning a pointer to a parent type to a pointer to a sublcass type also requires a cast.

These are called downcasts.

Casts[28] [top]

There are several kinds of casts in C++

Here are three:

We will look at some examples.

Examples[29] [top]

We will work through these examples in class.