Essential C++ # 05. Object-Oriented Programming

C++

Chapter 5. Object-Oriented Programming

The object-based programming model proves cumbersome when our application begins to be filled with class types that represent an is-a-kind-of instance of a type.

 

5.1. Object-Oriented Programming Concepts

📌Primary Characteristics of Object-Oriented Programming

  • Inheritance
  • Polymorphism

Inheritance allows us to group classes into families of related types, allowing for the sharing of common operations and data.

Polymorphism allows us to program these families as a unit rather than as individual classes, giving us greater flexibility in adding or removing any particular class.

 

📌Calling Convention

The parent is called the base class.

The child is called the derived class.

 

📌Abstract Base Class

The root of the class hierarchy is an abstract base class.

image-20220406102109496

[IMPORTANT!!!]⭐⭐⭐In an object-oriented program, we indirectly manipulate the class objects of our application through a pointer or reference of an abstract base class rather than directly manipulate the actual derived class objects of our application.

 

📌Key to Polymorphism and Dynamic Binding

Polymorphism and Dynamic Binding are supported ONLY when we are using a pointer or reference.

 

5.2. A Tour of Object-Oriented Programming

📌Example of LibraryMaterial

Base class:

Derived class:

Derived class of derived class:

//TODO - for const string& narrator() const {return _narrator;}

why the return type is const string&, what is the 2nd const?

 

5.3. Polymorphism without Inheritance

📌static_cast<T> to cast A to B

 

5.4. Defining an Abstract Base Class

📌Standard Procedure Design an Abstract Base Class

1️⃣ The first step is to identify the set of operations common to its children.

Therefore, we can make the following:

 

2️⃣ The next step is to identify which operations are type-dependent[^6] - that is, which operations require separate implementations based on the derived class type.

Those operations required separate implementation must declare with virtual. What's more, if there is no meaningful implementation of that function at current base class, must be declared as a pure virtual function[^7]. e.g.

For function that are common to all derived classes, no need to specify with virtual. e.g. The function which returns the maximum length of sequence.

Also, please take into account that: "A static member function cannot be declared as virtual".

 

3️⃣ The third step is to identify the access level of each operation.

This is very similar to C# which has private, protected, and public.

The derived class of num_sequence can access members declared with protected. While you cannot access them outside of this class.

 

📌Why class with pure virtual function is called "abstract" class?

Because its interface is incomplete, a class that declares one or more pure virtual functions cannot have independent class objects defined in the program. It can only serve as the subobject of its derived classes.[^8]

 

📌Design of constructor and destructor in Abstract Base Class

For Constructor

Why there is no constructor in num_sequence?🤔 There are no nonstatic data members within the class to initialize, therefore there is no real benefit to providing a constructor.

For Destructor

As a general rule, a base class that defines one or more virtual functions should always define a virtual destructor.

Mechanism Behind[IMPORTANT!!!]⭐: ps is a num_sequence base class pointer, but it addresses a Fibonacci-derived class object. When the delete expression is applied to a pointer to a class object, the destructor is first applied to the object addressed by the pointer. Then the memory associated with the class object is returned to the program's free store. In this case, the destructor invoked through ps must be the Fibonacci class destructor and not the destructor of the num_sequence class. That is, which destructor to invoke must be resolved at run-time based on the object actually addressed by the base class pointer. Therefore, we must declare the destructor virtual.[^9]

 

📌Recommended Way to Define a virtual Destructor👍

 

5.5. Defining a Derived Class

📌The Composite of a Derived Class

The derived class consists of 2 parts:

  • the subobject of its base class (e.g. the nonstatic base class data members)
  • the derived class portion (e.g. the nonstatic derived class data members)

 

📌Implement a Derived Class

A derived class MUST provide an implementation of each of the pure virtual functions inherited from its base class.⭐

See the following user code:

Later then, we have 2 options to retrofit our class:

  • define length() as virtual function in base class
  • define length() as member function in base class

Either way, we have to declare length() interface in base class. In real world design, this is an iterative process that evolves through experience and feedback from users.

 

📌virtual keyword needn't show up again in .cpp

Suppose you have the preceding member function, you can define in .cpp without virtual keyword again. This is a kind of different from C#.

 

📌Explicit definition can speed up compile-time

Suppose you want to implement the elem(int pos) function in Fibonacci class.

You may ask,🤔 gen_elems() has been declared in the base class, why do you explicitly add Fibonacci:: as prefix? Because you are implementing the Fibonacci::elem, so you do know you will use Fibonacci::gen_elems rather than asking compiler to figure out. Hence, it can speed up the whole process!!

 

📌Duplicate Function Name without virtual

Suppose we have following 2 functions with same name but without specifying virtual.

When you type the following:

Whenever a member of the derived class reuses the name of an inherited base class member, the base class member becomes lexically hidden within the derived class. Therefore, if you want to use the base class member function, you have to explicitly declare it.

You can invoke the check_integrity() of base class by num_sequence::check_integrity(pos). In C#, you can use the base keyword, like so:

 

📌Function Design Rule

With preceding code, we conclude that: "It is not a good practice, in general, to provide nonvirtual member functions with the same name in both the base and derived class."

 

5.6. Inheritance Hierarchy

There is nothing special in this section, few things I learned from the following code:

📌The Use of friend

I understand better what a friend means. Since operator<< requires a parameter which is an instance of num_sequence, but this block of code is also inside of the class num_sequence. Therefore, the num_sequence is a fresh element has not been declared yet. The friend keyword is to solve this problem.

 

📌Put the virtual function in a nonvirtual function

To prevent the derived class override both operator<< and print(), the designer put the virtual print() inside the nonvirtual operator<<. Smart.

 

5.7. How Abstract Should a Base Class Be?

📌What is the answer to this question?

In short, there is no absolute correct answer. This refers to Software Development.

 

📌Benefit of a Reference Member in a Base Class

A reference data member must be initialized within the constructor's member initialization list and, once initialized, can never be changed to refer to a different object.

 

📌Retrofit the num_sequence class

With preceding theory, we can have the following:

 

5.8. Initialization, Destruction and Copy

📌Good Practice of Initialization

A good practice will be a base class constructor initialize the members belong to base class. Then in the derived class, just use it in member initialization list.

 

5.9. Defining a Derived Class Virtual Function

📌The Declaration must match

Not match example❌

Not match but also error example❌

match example✔

match example but with different declaration✔

 

📌virtual function of derived class never invoked in base constructor

It is very easy to understand since the derived class hat not yet been initialized how can it offers help in its base class constructor.

 

📌Polymorphism only via reference and pointer

In C++, only pointers and references of the base class support object-oriented programming.

 

5.10. Run-Time Type Identification

The run-time type identification is very much the same as the System.Reflection in C#.

 

📌Return the name of such class

 

📌Check if the type is correct

The preceding syntax is exactly the same as in C#.

 

📌static_cast in C++

 

📌dynamic_cast in C++

 

 

 

Previous
Previous

Programming Abstraction in C++ # 01. Overview of C++

Next
Next

Essential C++ # 04. Object-Based Programming