Constructors are called when an instance of the class is created
Copy constructor
- A constructor that copies value from one instance of the object to another
- This is important because without a copy constructor, you would end up with two variables pointing to the same object.
- If one is deleted, the other doesn’t know it’s been deleted as well.
1 | class Passenger |
How to Invoke Constructors
1 | Passenger p1; //default constructor |
Enumerables
- Enumerables are used as a way to represent a category. Each “label” in an enumerable is associated with an int value, but as a programmer, typing the same would be the same as if we typed that specific int value.
Constructor using initializer list
- Initializer list avoids calling default constructor for no reason.
- It’s used when the arguments are not simple data types. For example, if the argument is another class called “Name”.
- Another use for initializer list is when you want to initialize variables from a base class to a derived class. The derived class might not have access to the base class private data members, so the only way the derived class can use base class data members is right before object initialization.
1 | Passenger::Passenger(const string& nm, MealType mp, string ffn) |
Destructors
- A destructor is a member function which is called automatically when the object is destroyed, when the program terminates or when memory is released
- A destructor of class Passenger is denoted by ~Passenger
- It does not return anything and does not take any arguments
- We need destructors to deallocate any memory used by data members
1 | class Vect { |
Classes and Memory Allocation Problem
1 | Vect a(100); //vector 'a' has a size of 100 |
First, Vect a(100)
allocates an array of 100 integers and a.data
points to this array.
Declaration Vect b=a
does not work as intended. - Intended: copies over every element of a to b, which results in a different vector of 100 integer values. - Actual: Since no copy constructor was provided, the system uses the default, which copies b.data = a.data
. This does not copy the contents of the array. All it does is copy the pointer to the array’s initial element. (Shallow copy) - Notice that data
here is a pointer variable.
Declaration Vect c
invokes the default constructor, which has a default size of 10. So only 10 element vect.
c = a
, once again, since no assignment operator is provided, it does a shallow copy, which means only the pointer of a
is copied to the first element of c
. - Moreover, we lost the pointer to c’s original 10-element array–resulting in a memory leak.
The Fix: Provide a copy constructor and **overload the assignment operator
The problems arose because we allocated memory, and we used the system’s default copy constructor and assignment operator.
If a class allocates memory, you should provide a copy constructor and assignment operator to allocate new memory for making copies. A copy constructor for a class T is typically declared to take a single argument, which is a constant reference to an object of the same class, that is, T(const T& t)
. As shown in the code fragment below, it copies each of the data members from one class to the other while allocating memory for any dynamic members.
1 | Vect::Vect(const Vect& a) { //copy constructor from a |
The assignment operator is handled by overloading the =
operator as shown in the next code fragment. The argument “a” plays the role of the object on the right side of the assignment operator.
The assignment operator deletes the existing array storage, allocates a new array of the proper size, and copies elements into this new array.
The if statement checks against the possibility of self assignment. (This can sometimes happen when different variables reference the same object.) We perform this check using the keyword this
. For any instance of a class object “this
” is defined to be the address of this instance. If this equals the address of a, then this
is a case of self assignment, and we ignore the operation. Otherwise, we deallocate the existing array, allocate a new array, and copy the contents over.
1 | Vect& Vect::operator=(const Vect& a) { //assignment operator from |
Notice that in the last line of the assignment operator we return a reference to the current object with the statement “return *this
.” Such an approach is useful for assignment operators, since it allows us to chain together assignments, as in “a=b=c.” The assignment “b=c” invokes the assignment operator, copying variable c to b and then returns a reference to b. This result is then assigned to variable a
The Upshot
- Everything class that allocate its own objects using
new
should:- Define a destructor to free any allocated objects
- Define a
copy constructor
, which allocates its own new member storae and copies the contents of member variables. - Define an
assignment operator
, which deallocates old storage, allocates new storage, and copies all member variables.
Friend Class
- Friend designation gives access rights to private members
1 | class Vector { |
Object Oriented Programming Goals:
Robustness:
Correct software that can catch errors and recover or gracefully terminate
Adaptability
Complex software will need to change with time to add new features
Reusability
After writing something complex, you want to be able to reuse it elsewhere
Inheritance
- Inheritance is a technique of reusing existing class definitions to derive new classes
- These new classes (called derived classes or subclasses or child classes) can inherit the attributes and behavior of the pre-existing classes (called base classes or superclasses or parent classes)
- Derived classes can add, delete, & modify methods and data members in their own definitions (known as overriding)
1 | class Person { |
Usage of Inheritance
An object of type Person can access the public members of Person. An object of type Student can access the public members of both classes. If a Student object invokes the shared print function, it will use its own version by default. We use the class scope operator (::) to specify which class’s function is used, as in Person::print and Student::print. Note that an object of type Person cannot access members of derived type, and thus it is not possible for a Person object to invoke the changeMajor function of class Student.
1 | Person person1("Mary", "12-345"); // declare a person |
Special attention to the print function for inheritance
C++ programmers often find it useful for a derived class to explicitly invoke a member function of a base class. For example, in the process of printing information for a student, it is natural to first print the information of the Person base class, and then print information particular to the student. Performing this task is done using the class scope operator.
1 | void Person::print() { |
Without the “Person::” specifier used above, the Student::print function would call itself recursively, which is not what we want.
No access to private members from Student
Protected Members:
Even though class Student is inherited from class Person, member functions of Student do not have access to private members of Person. For example, the following is illegal.
1 | void Student::printName() { |
Special access privileges for derived classes can be provided by declaring members to be “protected.” A protected member is “public” to all classes derived from this one, but “private” to all other functions. From a syntactic perspective, the keyword protected behaves in the same way as the keyword private and public. In the class example above, had we declared name to be protected rather than private, the above function printName would work fine.
Note on public, private and protected: Illustrating Class Protection/Derived Accessibility
Consider for example, three classes: a base class Base, a derived class Derived, and an unrelated class Unrelated. The base class defines three integer members, one of each access type.
Special access privileges for derived classes can be provided by declaring members to be “protected.”
1 | class Base { |
Member functions are always declared private
or protected
.
Protected members are commonly used for utility functions
, which may be useful for derived classes.
Inheritance Summary
- The amount of access, and levels of modification are controlled by specifying
public
,protected
, orprivate
in the derived class header.- Public inhertiance of a
derived class
preserves the access classes of thebase class
.- E.g.,
- private remains private.
- protected remains protected.
- public remains public.
- E.g.,
- Protected inheritance of a
derived class
treats:- public and protected members of the
base class
as protected. - Private members remain private.
- public and protected members of the
- Private inheritence of a
derived class
treats all members of the base class as private.
- Public inhertiance of a
Constructor and Destructors for Derived
Class hierarchies in C++ are constructed bottom-up:
base class first,
then its members,
then the derived class itself.
For this reason, the constructor for a base class needs to be called in the initializer list of the derived class.
The example below shows how constructors might be implemented for the Person and Student classes.
1 | Person::Person(const string& nm, const string& id) |
Only the Person(nm,id) call has to be in the initializer list.
The other initializations could be placed in the constructor function body ({…}), but putting class initializations in the initialization list is generally more efficient. Suppose that we create a new student object.
Suppose that we create a new student object
1 | Student* s1 = new Student("Carol", "34-927", "Physics", 2014); |
The following will happen respectively: (given the use of initializer of base class above) 1. Constructor for the student class first makes a function call to Person(“Carol, “34-927”) to initialize the Person base class. 2. Then, it initializes the major to “Physics” and the year to 2014.
Destructor For Derived Classes and Base Class
Classes are destroyed in reverse order. Derived classes are destroyed first, and then base classes.
For example:
1 | Person::˜Person() { . . . } // Person destructor |
Unlike constructors, the Student destructor does not need to (and is not allowed to) call the Person destructor.
Static Binding: Declared Type vs. Actual Type
When a class is derived from a base class, as with Student and Person, the derived class becomes a subtype of the base class, which means that we can use the derived class wherever the base class is acceptable.
For example, suppose that we create an array of pointers to university people.
1 | Person* pp[100]; // array of 100 Person pointers |
Nonetheless, C++ provides a way to achieve the desired dynamic effect using the technique we describe next
Dynamic Binding and Virtual Functions
As we saw above, C++ uses static binding
by default to determine which member function to call for a derived class.
Alternatively, in dynamic binding
, an object’s contents determine which member function is called.
To specify that a member function should use dynamic binding, the keyword virtual
is added to the function’s declaration.
1 | class Person { |
Dynamic binding is a powerful technique, since it allows us to create an object, such as the array pp above, whose behavior varies depending on its contents. This technique is fundamental to the concept of polymorphism
Virtual Destructors
RULE:
If a base class defines any virtual functions, it should define a virtual destructor, even if it is empty.
If we store types Person and Student in the array, it is important to call the appropriate destructor. It isn’t an issue for the example above (because nothing is stored dynamically).
If a Student class had allocated memory dynamically, the fact that the wrong constructor is called would result in a memory leak.
Polymorphism
Literally, “polymorphism” means “many forms.” In the context of object-oriented design, it refers to the ability of a variable to take different types. Polymorphism is typically applied in C++ using pointer variables. In particular, a variable p declared to be a pointer to some class S implies that p can point to any object belonging to any derived class T of S.
That is, p can take many forms, depending on the specific class of the object it is referring to. This kind of functionality allows a specialized class T to extend a class S, inherit the “generic” functions from class S, and redefine other functions from class S to account for specific properties of objects of class T.
Inheritance, polymorphism, and function overloading support reusable software. We can define classes that inherit generic member variables and functions and can then define new, more specific variables and functions that deal with special aspects of objects of the new class.
For example, suppose that we defined a generic class Person and then derived three classes Student, Administrator, and Instructor. We could store pointers to all these objects in a list of type Person*. When we invoke a virtual member function, such as print, to any element of the list, it will call the function appropriate to the individual element’s type.
Now consider what happens if both of these classes define a virtual member function a
, and let us consider which of these functions is called when we invoke p->a(). Since dynamic binding is used, if p points to an object of type T, then it invokes the function T::a. In this case, T is said to override function a from S. Alternatively, if p points to an object of type S, it will invoke S::a.
–Static binding determines the function call at compile time
–Dynamic binding delays the decision until run time
Polymorphisms: Specialization
- In short, the base class and the derived class have the same functions, but one or more functions of the same name can be altered to meet specific goals.
- Dog and bloodhound have a drink() and sniff() function.
- Bloodhounds have a much higher sniff() sensitivity, so sniff() is “specialized” and overrides the base class (Dog) sniff().
- Dog and bloodhound have a drink() and sniff() function.
Polymorphisms: Extension
- In short, the base class and the derived class have the same functions, but the derived class has extra functions. No overlap compared to specialization.
TLDR:
With polymorphism we can both specialize and extend a class - Specialize by overriding a function or implementing a virtual function - Extend by introducing new functions
Examples of Inheritance and Polymorphism
Define a base class, Progression
, which defines the “generic” numbers and functions of a numeric progression.
Why did we declare virtual for firstValue() and nextValue() ?
- It is our intention that in order to generate different progressions, derived classes will override one or both of the functions
firstValue()
andnextValue()
. For this reason, we have declared both functions to be virtual.- Furthermore, with every virtual function, a virtual destructor is provided to be safe.
1 | class Progression { |
Arithmetic Progression Class
1 | class ArithProgression : public Progression { |
Polymorphism is at work here. When a Progression pointer is pointing to an ArithProgression object, it will use the ArithProgression functions firstValue and nextValue.
**Even though the function printProgression in the base class is not virtual, it makes use of this polymorphism.
- Its calls to the firstValue and nextValue functions are implicitly for the “current” object, which will be of the ArithProgression class.**
Geometric Progression Class
Like ArithProgression, this new class inherits first
and cur
and the member functions firstValue()
and printProgression()
from base Class Progression. - A new member base
is added, which holds the base value, and the constructor initializes the base class with a starting value of 1 instead of a 0.
1 | class GeomProgression : public Progression { |
Fibonacci Progression Class
Each element of a Fibonacci series is the sum of the previous two elements. - Fibonacci progression (first = 0, second = 1): 0, 1, 1, 2, 3, 5, 8, . . .
1 | class FibonacciProgression : public Progression { |
1 | /* The initialization process is a bit tricky because we need to create a “fictitious” element that precedes the first element. Note that setting this element to the value (second − first) achieves the desired result.*/ |
Entire Progression Class Combined
1 |
|
Without virtual function, the output of the program in the previous slide will be different. Why?
All values will print the same as the Arithmetic progression with default increment.
Due to Static binding since the variable prog is of type Progression.
Other Types of Inheritance (Summary)
1 | class Base { // base class |
Dynamic Casting
Dynamic casting can only be applied to polymorphic objects, that is, objects that come from a class with at least one virtual function.dynamic cast < desired type > ( expression )
1 | Student* sp = dynamic cast(pp[1]); // cast pp[1] to |
Interfaces and Abstract Base Classes/Pure Virtual Functions
Abstract classes is a class only used as a base class for inheritance. It cannot be used to create instances directly. It’s like the abstract concept of a shape, but a shape in itself is not an object–but subclasses of shapes like triangles and squares are objects.
In C++, an abstract class is defined when one or more members are abstract or pure virtual.
Pure virtual is when a function is given “
=0
“ in place of its body.
- C++ does not allow the creation of a pure virtual function.
- As a result, the compiler will not allow the creation of objects of type Progression, since the function nextValue is “pure virtual.” However, its derived classes, ArithProgression for example, can be defined because they provide a definition for this member function.
1 | class Progression { // abstract base class |
Interfaces and Abstract Base Classes
While C++ does not provide a direct mechanism for defining interfaces for abstract data types–we can use abstract classes to achieve much of the same purpose.
1 | class Stack { // stack interface as an abstract class |
1 | class ConcreteStack : public Stack { // implements Stack |