C++ Tutorial
C++ Abstract Classes
A pure virtual function makes a class abstract. You cannot construct it; you inherit it.
A function that must be filled in
A virtual function with = 0 is pure virtual. The base class says the name and the signature. It does not say the body. Any class that contains a pure virtual function is abstract: the compiler will not let you make an object of that type.
You inherit the abstract class and write the missing function in each derived class. That is the contract: every concrete shape can answer kind(), but there is no generic shape sitting in memory with a fake answer. Include <string> when the function returns string.
virtual string kind() = 0
Write virtual string kind() = 0; in the base. In each derived class, writestring kind() override and return a label. override asks the compiler to check that you really replaced the base function.
Shape s; does not compile. Circle c; does, because Circle provideskind(). The polymorphism chapter used a base that still had a body. Here the base has none.
Two derived types, one Shape reference
You can still use the abstract type as a reference or a pointer. A function that takesShape& will accept a Circle or a Square. The call tokind() runs the derived version.
Example
#include <iostream>
#include <string>
using namespace std;
class Shape {
public:
virtual string kind() = 0;
};
class Circle : public Shape {
public:
string kind() override {
return "circle";
}
};
class Square : public Shape {
public:
string kind() override {
return "square";
}
};
void show(Shape& s) {
cout << s.kind() << endl;
}
int main() {
Circle c;
Square q;
show(c);
show(q);
return 0;
}Output is circle then square. The objects live on the stack.show never names Circle or Square. UncommentingShape s; inside main is a compile error.
Call kind on the objects directly
Direct calls work too. You do not need a helper. The reference version matters when one function must handle every kind of shape without a growing if chain.
Example
#include <iostream>
#include <string>
using namespace std;
class Shape {
public:
virtual string kind() = 0;
};
class Circle : public Shape {
public:
string kind() override {
return "circle";
}
};
class Square : public Shape {
public:
string kind() override {
return "square";
}
};
int main() {
Circle c;
Square q;
cout << c.kind() << endl;
cout << q.kind() << endl;
return 0;
}A vector of unique_ptr
A vector<Shape> is impossible: the vector would have to construct Shapeobjects. Store owning pointers instead. unique_ptr<Shape> from <memory>holds a derived object and frees it when the pointer dies. Give the base a virtual destructor so that delete through the base pointer runs the right cleanup.
Example
#include <iostream>
#include <memory>
#include <string>
#include <vector>
using namespace std;
class Shape {
public:
virtual string kind() = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
string kind() override {
return "circle";
}
};
class Square : public Shape {
public:
string kind() override {
return "square";
}
};
int main() {
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Circle>());
shapes.push_back(make_unique<Square>());
for (const unique_ptr<Shape>& s : shapes) {
cout << s->kind() << endl;
}
return 0;
}make_unique<Circle>() is C++14 and legal in C++17. s->kind() is a virtual call through the pointer. You do not write delete: unique_ptr does that when the vector is destroyed.
Rules that keep this small
- Pure virtual:
virtual string kind() = 0;in the base. - Each concrete class writes
kind()withoverride. - Do not construct the abstract type. Construct
CircleorSquare. - Pass
Shape&for stack objects. Useunique_ptr<Shape>when you need a list. - If you delete through
Shape*, the destructor inShapemust be virtual.
When a class should be abstract
Use an abstract base when every derived type must supply a behavior and a default in the base would be a lie. A shape that cannot say what it is should not exist. A file reader that cannot read should not exist. Shared fields and non-virtual helpers can still live in the base; only the required hook is pure.
If the base can provide a reasonable default, a plain virtual function is enough — that was the Animal example in the polymorphism chapter. Pure virtual is the stricter tool.
End of the C++ track
This is the last chapter. Return to /cpp for the full list, or to/cpp/examples for complete programs you can copy. Anything that still feels new compiles at /cpp/try.
Open the unique_ptr listing with Try it in C++. Add a third derived class with its ownkind(), push it into the vector, and run again. The loop does not change.
Worked examples
The short listings above are there so you can see the grammar. The programs here use the same statements on quantities that already have units: a speed, a pH, a count of bases. They are classroom numbers. Air resistance is ignored. g is 9.81 m/s² unless a line says otherwise.
Open them in the C++ editor at /cpp/try. Change one measurement and check whether the result still has the right unit.
Physics
An energy you cannot construct
pure virtual value() = 0 makes Energy abstract. You cannot write Energy e;. Kinetic fills in ½ m v². 2 kg at 3 m/s is 9 J. The base is the interface; the derived class is the formula.
KE = ½ m v²
Example
#include <iostream>
using namespace std;
class Energy {
public:
virtual double value() = 0;
virtual ~Energy() {}
};
class Kinetic : public Energy {
double m, v;
public:
Kinetic(double mass, double speed) : m(mass), v(speed) {}
double value() override { return 0.5 * m * v * v; }
};
int main() {
Kinetic ke(2.0, 3.0);
cout << ke.value() << " J" << endl;
return 0;
}Maths
A shape that must define area
You cannot construct Shape. Circle must implement area(). Unit circle, r = 1, A = π. The = 0 is the compiler enforcing the exam instruction: state the formula for this shape.
A = π r²
Example
#include <iostream>
using namespace std;
class Shape {
public:
virtual double area() = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
double r;
public:
Circle(double radius) : r(radius) {}
double area() override { return 3.14159 * r * r; }
};
int main() {
Circle c(1.0);
cout << c.area() << endl;
return 0;
}