Composition is a closely related alternative to inheritance. Composition involves constructing ("composing") classes from other classes, instead of inheriting traits from a parent class.
A common way to distinguish "composition" from "inheritance" is to think about what an object can do, rather than what it is. This is often expressed as "has a" versus "is a".
// Example solution for Circle class
#include <iostream>
#include <cmath>
#include <assert.h>
// Define PI
#define PI 3.14159;
// Define LineSegment struct
struct LineSegment {
// Define protected attribute length
public:
double length;
};
// Define Circle class
class Circle {
public:
Circle(LineSegment& radius);
double Area();
private:
LineSegment& radius_;
};
// Declare Circle class
Circle::Circle(LineSegment& radius) : radius_(radius) {}
double Circle::Area()
{
return pow(Circle::radius_.length, 2) * PI;
}
// Test in main()
int main()
{
LineSegment radius {3};
Circle circle(radius);
assert(int(circle.Area()) == 28);
}
In C++, friend
classes provide an alternative inheritance mechanism to derived classes. The main difference between classical inheritance and friend inheritance is that a friend
class can access private members of the base class, which isn't the case for classical inheritance.
// Example solution for Rectangle and Square friend classes
#include <assert.h>
class Rectangle;
class Square {
public:
Square(int side) : side_(side) {}
private:
friend class Rectangle;
int side_;
};
class Rectangle {
public:
Rectangle(const Square& a);
int Area() const;
private:
int width_;
int height_;
};
// Define a Rectangle constructor that takes a Square
Rectangle::Rectangle(const Square& a) : width_(a.side_), height_(a.side_) {}
// Define Area() to compute area of Rectangle
int Rectangle::Rectangle::Area() const {
return width_*height_;
}
int main()
{
Square square(4);
Rectangle rectangle(square);
assert(rectangle.Area() == 16);
}
Polymorphism is means "assuming many forms".
In the context of object-oriented programming, polymorphism describes a paradigm in which a function may behave differently depending on how it is called. In particular, the function will perform differently based on its inputs.
Polymorphism can be achieved in two ways in C++: overloading and overriding.
Overloading
#include <ctime>
class Date {
public:
Date(int day, int month, int year) : day_(day), month_(month), year_(year) {}
Date(int day, int month) : day_(day), month_(month) // automatically sets the Date to the current year
{
time_t t = time(NULL);
tm* timePtr = localtime(&t);
year_ = timePtr->tm_year;
}
private:
int day_;
int month_;
int year_;
};
Operator Overloading
You can choose any operator from the ASCII table and give it your own set of rules!. In order to overload an operator, use the operator
keyword in the function signature:
Complex operator+(const Complex& addend) {
//...logic to add complex numbers
}
#include <assert.h>
// TODO: Define Point class
class Point {
// TODO: Define public constructor
public:
Point (int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
// TODO: Define + operator overload
Point operator+(const Point& addend) {
Point c;
c.x = x + addend.x;
c.y = y + addend.y;
return c;
}
// TODO: Declare attributes x and y
int x, y;
};
// Test in main()
int main() {
Point p1(10, 5), p2(2, 4);
Point p3 = p1 + p2; // An example call to "operator +";
assert(p3.x == p1.x + p2.x);
assert(p3.y == p1.y + p2.y);
}
Virtual Functions
A pure virtual function is a virtual function that the base class declares but does not define.
A pure virtual function has the side effect of making its class abstract. This means that the class cannot be instantiated. Instead, only classes that derive from the abstract class and override the pure virtual function can be instantiated.
class Shape {
public:
Shape() {}
virtual double Area() const = 0;
virtual double Perimeter() const = 0;
};
Overriding
"Overriding" a function occurs when:
- A base class declares a [
virtual
function](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#glossary function). - A derived class overrides that virtual function by defining its own implementation with an identical function signature (i.e. the same function name and argument types).
class Animal {
public:
virtual std::string Talk() const = 0;
};
class Cat {
public:
std::string Talk() const override { return std::string("Meow"); }
};
Specifying a function as override
is good practice, as it empowers the compiler to verify the code, and communicates the intention of the code to future users.
Function Hiding
Function hiding is closely related, but distinct from, overriding.
A derived class hides a base class function, as opposed to overriding it, if the base class function is not specified to be virtual
.
class Cat { // Here, Cat does not derive from a base class
public:
std::string Talk() const { return std::string("Meow"); }
};
class Lion : public Cat {
public:
std::string Talk() const { return std::string("Roar"); }
};
The distinction between overriding and hiding is subtle and not terribly significant, but in certain situations hiding can lead to bizarre errors, particularly when the two functions have slightly different function signatures.
Multiple Inheritance
- "Use multiple inheritance to represent multiple distinct interfaces"
- "Use multiple inheritance to represent the union of implementation attributes"
Multiple inheritance is tricky and can present an issue known as the diamond problem, it is when both derived class derived the same virtual function from a bas class.
Templates
Templates enable generic programming by generalizing a function to apply to any class. Specifically, templates use types as parameters so that the same implementation can operate on different data types. It is mandatory to put the template<>
tag before the function signature, to specify and mark that the declaration is generic.
template <typename Type> Type Sum(Type a, Type b) { return a + b; }
int main() { std::cout << Sum<double>(20.0, 13.7) << "\n"; }
// TODO: Create a generic function Product that multiplies two parameters
template <typename T>
T Product(T a, T b) {
return a * b;
}
int main() {
assert(Product<int>(10, 2) == 20);
}
Template do not have the ability to specify the requirement of its parameterized types.
Template deduction
From C++ 17 onwards, deduction occurs when you instantiate an object without explicitly identifying the types. Instead, the compiler "deduces" the types. This can be helpful for writing code that is generic and can handle a variety of inputs.
std::vector v{1,2,3}; // without explicitly initiate type