oop in c++
OOP in C++
OOP is often technically simplified as a principle bundling and encapsulate Data with Logic in Objects. However combining data and behavior can result in tight coupling.
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. Dr. Alan Kay
The Big Idea is: Messages can passed between objects. [1] An Object can receive Messages and can respond to the sender.
C++ moved to a more generic style of programming. The Standard Libary (STL) uses also a lot of FP Techniques.
The keyword class
and signature features like inheritance
(subclasses) are linked to the work of Ole-Johan Dahl and Kristen Nygaard (Simula).
Access Modifiers
The access specifiers to define the accessibility of members:
- public
- private
- protected
It is a good Idea to prefere to keep members of a class private.
For example day
, month
, year
of a Date Class.
This allows Invariations and hides the implementation.
It may also allow Caching (lazy-loading) or Iterators.
For example a day setter sets the private day_
of a class Date
.
void Date::Day(int day) {
// Invariation
if (day >= 1 && day <= DaysOfMonth())
day_ = day;
}
Instead of date.day = 15
, date.Day(15)
allows a Validation.
A C++20 std::chrono
example that uses another approach and implementation class day
.
std::chrono::day d1{15};
if (d1.ok()) {
std::cout << " is a valid day.\n";
}
The Date Class could also use std::chrono
, because private members with accessors are hiding the implementation and the public interface stays stable and constant.
Accessor and Mutator methods
Accessors (or Getter Methods) and Mutators (or Setter Methods) are to control the private members mutation. This allows invariant logic (for example: conditional set valid values only).
Encapsulation
Keep the internal State of an object private and only through an Interface. Allows Iterators or Invariations over Setters.
Encapsulation may hide underlying data strucures which may contain logic. Direct usage of Objects that are not relevant in the Context causes unnecessary tight coupling.
person.head.Mouth().Talk("Hello")
If the person forget its head, things would just work, because “Say” handles how to say “Hello”:
person.Say("Hello");
A famous Style Guideline is:
“Only talk to your immediate friends.” Law of Demeter
SelfEncapsulation
The Usage of Encapsulation for mutable internal Data. Only the public interface or introduced private acccessors are allowed to mutate internal data.
Mutation of accessible members:
bool Date::IsLeapYear() {
if (year_ % 4 == 0) {
return false;
}
else if (year_ % 100 == 0) {
return true;
}
else if (year_ % 400 != 0) {
return false;
}
return true;
}
Access through Accessors (SelfEncapsulation):
bool Date::IsLeapYear() {
if (Year() % 4 == 0) {
return false;
}
else if (Year() % 100 == 0) {
return true;
}
else if (Year() % 400 != 0) {
return false;
}
return true;
}
Initializer List
Initilize Values in the Constructor.
Date(int day, int month, int year) : day_(day), month_(month), year_(year) {}
Initializer list doesn’t allow invariants.
Date(int day, int month, int year) : day_(day), month_(month), year_(year) {
ValidateMembers();
}
Initializer lists are a way to initiliaze const members (for example a imutable class Birthday
).
Static variables and methods
Static variables are Class-Variables, that are not tied to an instance of an object and like namespaced global Variables.
A static const members has to be initilized out of the class or as constexp.
Polymorphism
Polymorphism allows different Implementations (Variants) with a similiar interface.
Polymorphism is often based on virtual (overloaded) functions. Like a virtual table of objects and you set the pointer to the implementation with an overloaded function that the dispatcher calls.
A new Kind of Polymorphism, possible in C++17, is over std::visit
to visit a variant of std::variant
at runtime and the best matching implementation (variant) get called.
Inheritance
Reuse and inherit features (attributes) from a exisiting class.
A Car can be FireDepartmentCar with special Abilities, but both are also an Vehicle. In Object Hierachy, Vehicle <- is_a Car <- is_a FireDepartmentCar over Inheritance. A Subtype is therefore also a “specialized” BaseType.
Building complex hierachies may fail, because a subtype may be harder to replace (Liskov Substitution Principle). For example when the FireDepartmentCar extends EmergencyGadgets, which also could get extended by an HospitalHelicopter (which is not a Vehicle) … do the same Emergency-Policies (can ignore traffic lights etc.) apply? Is it just a flyable Vehicle?
In that Case it may better to implement an abstract EmergencyGadget[1].
Access specifier of derived Class
- public inheritance
- private
- protected
Multiple Inheritance
To inherit features from more than one base class is possible.
Use Case: a Class that inherits different implementations that is not based on a single-rooted hierachy to represent multiple distinct interfaces.
Diamond Conflict.
AmphibiousCar <- is_a Boat, is_a Car <- is_a Vehicle
For example an amphibious Car, that inherits Boat (Boat#move
) and Car (Car#move
), both implement move
, inherited from Vehicle (virtual Vehicle#move
): Which parent implemenation of Vehicle#move
get executed?
Generic Programming
A generic algorithm for differnt data-types with templates.
Templates can be combined with functional programming patterns and techniques.
For example the STL uses generic and functional programming Techniques.
Callable objects are used in many STL algorithms and generic template vector
.
Functional Programming
Often in a more verbose syntax, than in a GarbageCollected-Language, but without the Garbage (Resource acquisition is initialization (RAII)).
Beyond C function pointers:
- C++03: named Functors ()
- C++11: lambdas (and IIFE - Immediately Invoked Function Expression)
- C++14: polymorphic anonymous functions
historical WhatIs? Paper
C++, formerly “C with Classes”, is object-oriented programming on top of C and follows more a Simula Model and not a dynamically typed language such as Smalltalk.
Object-oriented Programming is programming using inheritance. Data abstraction is programming using user defined type. With few exceptions, object-oriented Programming can and ought to be a superset of data abstraction. These techniques need proper support to be effective. Data abstraction primarily needs support in the form of language features, and object-oriented programming needs further support from a programming environment. To be general purpose, a language supporting data abstraction or object-oriented programming must enable effective use of traditonal hardware.
([1] What is “object-oriented programming”? Stroustrup 1986)
There is also an powerful example of virtual functions (“parametrized types”) under 4.2 Type Checking.