Classes and Structs
Classes and Structs
Bundle data with behavior, maintain invariants, and design small types cleanly.
Classes and Structs
Bundle data with behavior, maintain invariants, and design small types cleanly.
class BankAccount {
public:
explicit BankAccount(double balance) : balance_{balance} {}
void deposit(double amount) {
balance_ += amount;
}
double balance() const {
return balance_;
}
private:
double balance_{};
};
They let you keep related data and rules together.
That matters because valid state is easier to maintain when the type itself enforces the rules.
constexplicitclass BankAccount {
public:
explicit BankAccount(double balance) : balance_{balance} {}
void deposit(double amount) {
if (amount > 0.0) {
balance_ += amount;
}
}
bool withdraw(double amount) {
if (amount > balance_) {
return false;
}
balance_ -= amount;
return true;
}
double balance() const {
return balance_;
}
private:
double balance_{};
};
This is better than exposing a public balance field and expecting every caller to remember the same validity checks.
class TemperatureRange {
public:
TemperatureRange(double low, double high) : low_{low}, high_{high} {
if (high_ < low_) {
std::swap(low_, high_);
}
}
bool contains(double value) const {
return value >= low_ && value <= high_;
}
double width() const {
return high_ - low_;
}
private:
double low_{};
double high_{};
};
The constructor establishes a usable invariant immediately, and the member functions keep the logic close to the stored data.
struct vs classUse struct for simple aggregates with obvious fields and little behavior. Use class when you need stronger encapsulation or invariants.
struct Point {
int x{};
int y{};
};
This is a good struct because it is just a small value object with obvious fields.
If your members already manage themselves, let the compiler generate constructors, destructors, and assignment operators.
struct UserProfile {
std::string name;
std::vector<int> scores;
};
If your members already manage memory and cleanup safely, avoid writing custom destructor, copy, or move operations unless you genuinely need special behavior.
When a type directly owns a raw resource, you typically need to define (or delete) all five special member functions.
class Buffer {
public:
explicit Buffer(std::size_t size)
: data_{new int[size]}, size_{size} {}
~Buffer() { delete[] data_; }
Buffer(const Buffer& other)
: data_{new int[other.size_]}, size_{other.size_} {
std::copy(other.data_, other.data_ + size_, data_);
}
Buffer& operator=(const Buffer& other) {
if (this != &other) {
auto* copy = new int[other.size_];
std::copy(other.data_, other.data_ + other.size_, copy);
delete[] data_;
data_ = copy;
size_ = other.size_;
}
return *this;
}
Buffer(Buffer&& other) noexcept
: data_{other.data_}, size_{other.size_} {
other.data_ = nullptr;
other.size_ = 0;
}
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
private:
int* data_{};
std::size_t size_{};
};
In practice, prefer wrapping raw resources in a smart pointer or standard container so you can rely on the rule of zero instead.
For many domain objects, a small value type with clear fields, comparisons, and no manual resource management is a better design than a large inheritance hierarchy.
struct Point {
int x{};
int y{};
auto operator<=>(const Point&) const = default;
};
This is a good value type: easy to copy, easy to compare, and free of manual lifetime management.
explicit for single-argument constructors unless implicit conversion is clearly desirableconst?new and delete?struct be enough, or is encapsulation actually required?structclassstruct Point {
int x{};
int y{};
};
int main() {
Point p{3, 4};
return p.x + p.y;
}
Add a member function that keeps the invariants next to the data. That is the simplest way to move from passive structs to useful types.
struct Point {
int x{};
int y{};
int magnitude_squared() const {
return x * x + y * y;
}
};