Classes bundle data and behaviour in C++ by giving a name to a group of related values and the operations that act on them. The class is the blueprint; the object is a particular vehicle built from it. Once you can write a class that owns its own data and refuses to let anything else corrupt it, you have the foundation everything else in object-oriented C++ is built on.
A class is a type with an implementation
A class is a user-defined type. It brings together member variables, which hold the state, and member functions, which operate on that state.
#include
#include
class Vehicle {
public:
std::string registration;
int mileage;
void describe() const {
std::cout << registration << " has done " << mileage << " miles\n";
}
};
int main() {
Vehicle van;
van.registration = "VN-4471";
van.mileage = 18200;
van.describe();
return 0;
}The output is a single line describing the van:
VN-4471 has done 18200 milesTwo things are worth pausing on. First, a class does not create anything; it describes what an object of that class will contain. Second, the members are accessed with the dot operator because van is an object, not a pointer.
Objects are instances of a class
Declaring a variable of a class type creates an object. Several objects of the same class are independent of each other, each with its own copy of the state.
#include
#include
class Vehicle {
public:
std::string registration;
int mileage;
};
int main() {
Vehicle van;
van.registration = "VN-4471";
van.mileage = 18200;
Vehicle truck;
truck.registration = "TR-0093";
truck.mileage = 74000;
std::cout << van.registration << ": " << van.mileage << " miles\n";
std::cout << truck.registration << ": " << truck.mileage << " miles\n";
return 0;
}The output shows the two objects holding entirely separate data:
VN-4471: 18200 miles
TR-0093: 74000 milesMember functions belong to the object
A member function has an implicit access to the object it is called on, which is what makes it different from a free function.
#include
#include
class Vehicle {
public:
std::string registration;
int mileage;
void record_trip(int miles) {
mileage += miles;
}
int remaining_service_interval(int service_every) const {
return service_every - (mileage % service_every);
}
};
int main() {
Vehicle van;
van.registration = "VN-4471";
van.mileage = 18200;
van.record_trip(150);
std::cout << "mileage now: " << van.mileage << '\n';
std::cout << "service in: " << van.remaining_service_interval(10000) << " miles\n";
return 0;
}The output shows the state updated by the member function and the derived value computed from it:
mileage now: 18350
service in: 1650 milesThe const at the end of remaining_service_interval says the function does not change the object. That is a promise the compiler checks.
Constructors and initialisation
Leaving the members uninitialised means the object holds garbage. A constructor sets the members to a sensible starting state every time an object is created.
#include
#include
class Vehicle {
public:
std::string registration;
int mileage;
Vehicle() : registration("UNASSIGNED"), mileage(0) {
std::cout << "a vehicle was created\n";
}
Vehicle(const std::string& reg, int miles)
: registration(reg), mileage(miles) {
std::cout << "a vehicle was created for " << registration << '\n';
}
};
int main() {
Vehicle van;
Vehicle truck("TR-0093", 74000);
return 0;
}The output shows the constructor running for each object:
a vehicle was created
a vehicle was created for TR-0093The colon after the parameter list starts the member initialiser list, and it is the only correct place to initialise members that are not built-in types. Assigning inside the body instead would construct the member first and then overwrite it.
Static members belong to the class
A static member is shared by every object of the class. It is the natural place for a counter or a registry of all vehicles built.
#include
#include
class Vehicle {
public:
std::string registration;
int mileage;
Vehicle() : registration("UNASSIGNED"), mileage(0) {
++fleet_size;
}
static int fleet_size;
private:
static int next_serial;
};
int Vehicle::fleet_size = 0;
int Vehicle::next_serial = 1000;
int main() {
Vehicle van;
Vehicle truck;
Vehicle bus;
std::cout << "vehicles built: " << Vehicle::fleet_size << '\n';
return 0;
}The output shows the shared counter:
vehicles built: 3Note the syntax for defining a static member outside the class: int Vehicle::fleet_size = 0;. Before C++17 that was mandatory, and it is still the form most code uses.
The class as a design tool
The point of a class is not to save typing. It is to make illegal states unrepresentable. A Vehicle whose mileage is only ever changed through record_trip cannot end up with a negative mileage, because nothing lets you assign to it directly.
That is the difference between a bag of fields and a class. The class owns an invariant, and every object of that class keeps it.
class versus struct
The only technical difference between a class and a struct in C++ is the default access: members of a class are private by default, members of a struct are public. Everything else — constructors, destructors, inheritance, member functions — works identically for both.
That makes the choice a matter of intent. Use a struct for a simple bundle of related data with no behaviour worth protecting, such as a plain record. Use a class when the data has rules that must hold and you want the compiler to enforce them.
Many codebases use struct for everything and make every member public explicitly, which keeps the intent visible at the top of the type. Others ban the distinction and always write class. Either is defensible; what matters is that the choice is consistent within a project and that the access level of every member is stated explicitly rather than left to a default.
Quick reference
- A class is a user-defined type; an object is an instance of it.
- Member variables hold state; member functions act on it.
- Use the dot operator for objects and the arrow operator for pointers.
- Each object has its own copy of the member variables.
- A constructor initialises the members; use the member initialiser list.
- A
staticmember is shared by every object of the class.
Try it yourself: write a [[Vehicle]] class with a registration, a mileage and a fuel level, plus a constructor and a method that records a trip and reduces the fuel. Then create three vehicles, give them different data, and print a summary of each. Finally, add a [[static]] counter of how many vehicles exist and check it after creating the three.