Classes, Structs & Encapsulation
Understand the difference between struct and class, access specifiers, encapsulation, const member functions, static members, and modern aggregate initialization.
What Is a Class?
A class in C++ is a user-defined type that bundles data (member variables) and operations on that data (member functions) into a single unit. This bundling is called encapsulation — one of the four pillars of OOP.
The key insight is that a class defines an invariant: a set of rules that must always be true about its internal state. For example, a Date class might require that the month is always between 1 and 12. Encapsulation lets you enforce these invariants by controlling access to internal state.
struct vs class
In C++, struct and class are almost identical. The only difference is the default access specifier: struct defaults to public, while class defaults to private. Convention: use struct for plain data aggregates, class when you have invariants to maintain.
#include <iostream>
#include <string>
// struct: members are public by default
struct Point {
double x; // public
double y; // public
};
// class: members are private by default
class BankAccount {
std::string owner; // private
double balance; // private
public:
BankAccount(const std::string& name, double initial)
: owner{name}, balance{initial} {}
double get_balance() const { return balance; }
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
};
int main() {
Point p{3.0, 4.0}; // aggregate initialization
std::cout << p.x << ", " << p.y << '\n';
BankAccount acct{"Alice", 1000.0};
acct.deposit(500.0);
std::cout << acct.get_balance() << '\n'; // 1500
// acct.balance = -999; // ERROR: private member
return 0;
}Access Specifiers & the this Pointer
C++ provides three access levels: public (anyone can access), private (only the class and its friends), and protected (the class, its friends, and derived classes). The this pointer is an implicit pointer to the current object available inside every non-static member function.
#include <iostream>
#include <string>
class Employee {
private:
std::string name_;
int id_;
protected:
double salary_; // accessible to derived classes
public:
Employee(const std::string& name, int id, double salary)
: name_{name}, id_{id}, salary_{salary} {}
// const member function: promises not to modify the object
const std::string& get_name() const { return name_; }
int get_id() const { return id_; }
// 'this' pointer usage — return *this for method chaining
Employee& set_name(const std::string& name) {
this->name_ = name; // 'this->' disambiguates
return *this;
}
Employee& set_id(int id) {
this->id_ = id;
return *this;
}
void print() const {
std::cout << "Employee{" << name_ << ", #" << id_
<< ", $" << salary_ << "}\n";
}
};
int main() {
Employee e{"Bob", 42, 75000.0};
e.set_name("Robert").set_id(43); // method chaining via *this
e.print(); // Employee{Robert, #43, $75000}
return 0;
}Static Members & friend
Static members belong to the class, not to any instance. Use them for shared state or utility functions. The friend keyword grants a non-member function or another class access to private members — use sparingly, as it breaks encapsulation.
#include <iostream>
class Widget {
int value_;
// C++17: inline static — define in header, no .cpp needed
inline static int count_ = 0;
public:
explicit Widget(int v) : value_{v} { ++count_; }
~Widget() { --count_; }
static int get_count() { return count_; }
// friend function can access private members
friend std::ostream& operator<<(std::ostream& os, const Widget& w);
};
std::ostream& operator<<(std::ostream& os, const Widget& w) {
return os << "Widget(" << w.value_ << ')'; // accesses private value_
}
int main() {
Widget a{10}, b{20};
std::cout << Widget::get_count() << '\n'; // 2
std::cout << a << '\n'; // Widget(10)
{
Widget c{30};
std::cout << Widget::get_count() << '\n'; // 3
}
std::cout << Widget::get_count() << '\n'; // 2
return 0;
}Aggregate Initialization & Designated Initializers (C++20)
An aggregate is a class or struct with no user-declared constructors, no private/protected non-static data members, no virtual functions, and no virtual base classes. C++20 adds designated initializers, letting you name which members you are initializing.
#include <iostream>
#include <string>
struct Config {
std::string host = "localhost";
int port = 8080;
bool use_tls = false;
int max_connections = 100;
};
int main() {
// Traditional aggregate initialization
Config c1{"example.com", 443, true, 200};
// C++20 designated initializers — much more readable
Config c2{
.host = "api.example.com",
.port = 443,
.use_tls = true
// .max_connections uses default value 100
};
std::cout << c2.host << ':' << c2.port << '\n';
std::cout << "TLS: " << std::boolalpha << c2.use_tls << '\n';
std::cout << "Max: " << c2.max_connections << '\n'; // 100
return 0;
}Make data members private by default and expose only what is needed through a minimal public interface. If a class has no invariant to maintain (it's just a bundle of data), use a struct with public members instead. Do not add getters and setters for every field — that defeats the purpose of encapsulation. Instead, provide meaningful operations like deposit() and withdraw() rather than set_balance().
If a member function does not modify the object's state, mark it const. Without const, you cannot call the function on a const object or through a const reference — which happens all the time when passing objects by const&. A missing const on an accessor is a common source of compilation errors in larger codebases.
structandclassdiffer only in default access:structis public,classis private- Encapsulation protects class invariants — use
privatedata with a meaningful public interface - Mark non-mutating member functions
constso they work withconstreferences - Use
inline static(C++17) for static members defined in headers - C++20 designated initializers make aggregate initialization readable and self-documenting
friendshould be used sparingly — typically for operator overloads that need private access
Quiz — Test Your Knowledge
(15 XP)1. What is the ONLY difference between `struct` and `class` in C++?
2. Why should you mark accessor member functions as `const`?
3. What does `inline static int count_ = 0;` achieve in C++17?