This lesson is a quick introductory tour of classes, structs, and enums within C++. It is not intended for those who are entirely new to programming. Rather, the people who may find it usefulย include:
It summarises several lessons from our introductory course. Those looking for more thorough explanations or additional context should consider completing Chapters 3, 4, and 8 of thatย course.
In C++, classes are created likeย this:
class Character {
// Member Variable
int Health{100};
// Member Function (Method)
void TakeDamage(int Damage) {
Health -= Damage;
}
}
// Creating Objects from the class
Character Frodo;
We can control how accessible class variables and methods are. This allows us to have a "public" part of our objects - an interface which we expect other objects to interactย with.
Meanwhile, we can restrict access to other methods and variables, ensuring theyโre only available to internal code. This makes our objects easier to use, and our classes simpler toย create.
C++ has three accessย levels:
public
members are available to be called by any code that has access to one of our class instancesprotected
members are only accessible to functions within the class, or any class that inherits from it. Inheritance is covered in the next sectionprivate
members are only accessible to functions within the classThere can be any number of specifiers within a class. Members have the accessibility defined by the closest proceedingย specifier.
By default, class members are private
.
class Character {
int a; // Private
public:
int b; // Public
int c; // Public
protected:
int d; // Protected
private:
int e; // Private
public:
int f; // Public
};
int main() {
Character Player;
// Not allowed - a is private
Player.a;
// Allowed - b is public
Player.b;
}
error: 'Character::a': cannot access
private member declared in class 'Character'
Where required, getters and setters can be implemented manually, allowing us to maintain class invariants like "the Health
of a Character
is never negative":
#include <iostream>
class Character {
public:
int GetHealth(){ return Health; }
void SetHealth(int NewValue){
if (NewValue < 0) {
Health = 0;
} else {
Health = NewValue;
}
}
private:
int Health;
};
int main(){
Character Player;
// Not allowed - Health is private
// Player.Health = -10;
// Must use the setter instead:
Player.SetHealth(-10);
std::cout << "Health: " << Player.GetHealth();
}
Health: 0
Our classes can inherit from other classes likeย this:
class Character {};
class Dragon : Character {};
Inheritance can also be private, protected, or public. This sets the maximum access level of inherited members,ย ie:
public
inheritance lets inherited members maintain their original access levelprotected
inheritance changes inherited public
members to protected
private
inheritance makes all inherited members private
By default, C++ inheritance is private
. The most useful, and most similar to other programming languages, is publicย inheritance:
class Character {};
class Dragon : public Character {};
C++ supports multiple inheritance and abstract classes.
Pure virtual classes are also available, which cover similar use cases to interfaces from other programmingย languages.
All three of these concepts are introduced later in thisย course
In C++, structs are almost identical to classes. The only difference is that by default, struct members are public
struct Vector3 {
float x; // Public
float y; // Public
float z; // Public
};
Vector3 MyPosition;
Despite the similarities, by convention, we prefer to use classes for complex requirements and restrict our use of structs to simpler useย cases.
For example, something requiring inheritance, references to other complex types, and lots of methods should probably be a class
. Something to hold a few primitive values together should probably be a struct
.
Constructors are functions that are automatically called when we create objects from our classes andย structs.
A default constructor does not require any arguments (ie, it does not have any parameters, or all of its parameters areย optional)
Our classes come with an implicit default constructor, which is called when we write code likeย this:
// Call the default constructor
Vector MyVector;
We can define custom constructors for our classes and structs by creating a member function with no return type, and the same name as our class or struct. For example, a constructor for the Vector
struct would also be called Vector
:
struct Vector {
// Initialise all components to the same value
Vector(float Value) {
x = Value;
y = Value;
z = Value;
}
float x;
float y;
float z;
};
Once we define any custom constructor, the implicit default constructor isย deleted:
// Call custom constructor
Vector MyVector{4.0f};
// Compilation error
Vector AnotherVector;
'Vector': no default constructor available
We can re-add it if desired, simply by providing a constructor that requires no arguments, or explicitly re-adding the default implementation using thisย syntax:
struct Vector {
Vector() = default;
// ...
};
In general, we can define as many constructors as we want, as long as their parameter lists are sufficiently unique such that the compiler knows which constructor weโre calling each time we initialize anย object:
#include <iostream>
struct Vector {
// The default constructor
Vector(){
std::cout << "Default Constructor\n";
}
// Initialise all components to the same value
Vector(float Value){
std::cout << "(float) Constructor\n";
x = Value;
y = Value;
z = Value;
}
// Construct from another vector and length
Vector(Vector OtherVector, float Length){
std::cout <<
"(Vector, float) Constructor\n";
// ...
}
float x;
float y;
float z;
};
int main(){
Vector A;
Vector B{1.4f};
Vector C{B, 1.f};
}
Default Constructor
(float) Constructor
(Vector, float) Constructor
Where a constructor is initializing member variables, the preferred way of doing this is through a member initializer list. This is a unique piece of syntax between our constructor heading and body. We add a colon :
followed by a list of comma-separatedย initializations.
Below, our constructor is initializing x
, y
, and z
to 1.f
, 2.f
, and 3.f
using a member initializerย list:
struct Vector {
Vector() :
// Member Initialiser List
x{1.f}, y{2.f}, z{3.f}
{
// Constructor Body
std::cout << "Constructing!";
}
float x;
float y;
float z;
};
Member initializer lists can use expressions, as well as constructorย parameters:
struct Vector {
Vector(float x) :
// Member Initialiser List
x{x},
y{1.f + 2.f},
z{GetRandomFloat()}
{
// Constructor Body
std::cout << "Constructing!";
}
float x;
float y;
float z;
};
When we have code we need to run when an object is destroyed, our classes and structs can define a destructor. A destructor uses the ~
symbol followed by the class/structย name:
struct Vector {
~Vector() {
std::cout << "Destructing!";
}
};
Destruction of objects, and the object life cycle more generally, is covered later in thisย course.
When all members of a class or struct are public
, we can use aggregate initialization to provide values for those members when constructing anย object:
struct Vector {
float x;
float y;
float z;
};
Vector MyVector{1.1f, 5.8f, 2.f};
We can think of structured binding as aggregate initialization in reverse. If we have an object, we can extract all its public members as variables in a single expression. Rather than doingย this:
float x{MyVector.x};
float y{MyVector.y};
float z{MyVector.z};
We can instead doย this:
auto [x, y, z] { MyVector };
When using structured binding, we must use auto
rather than specifying the variableย types.
In C++, enums are implemented in the followingย way:
enum class DamageType {
Fire,
Frost,
Arcane
}
Access to the values is through the scope resolutionย operator:
DamageType Weakness{DamageType::Fire};
We can add a using enum
statement if we want to reduce the need to qualify the enum name within the scope where the using
statement is inย effect:
using enum DamageType;
DamageType Weakness{Fire};
Classes, structs, and enums are how we create custom types in C++. They allow you to encapsulate related data and functionality into reusable and modular components. Keyย takeaways:
A crash tour on how we can create custom types in C++ using classes, structs and enums
Apply what we learned to build an interactive, portfolio-ready capstone project using C++ and the SDL2 library
Apply what we learned to build an interactive, portfolio-ready capstone project using C++ and the SDL2 library
Free, unlimited access