Classes and Objects
|
This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com. This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Members, Constructors, and Member-Initializer Lists
#include <string>
class Person {
public:
Person(std::string name, int age)
: name_(std::move(name)), age_(age) {} // member-initializer list -- initializes, doesn't assign
const std::string& name() const { return name_; }
int age() const { return age_; }
private:
std::string name_;
int age_;
};
Members are always initialized in declaration order, regardless of the order listed in the
member-initializer list (a mismatch here is a common -Wreorder warning) — prefer using std::move for
by-value parameters that are stored, exactly as name is above.
Special Member Functions and the Rule of Zero/Three/Five
Six functions are "special": default constructor, destructor, copy constructor, copy-assignment, move constructor, move-assignment. Declaring any of the copy/move/destructor set changes which of the others the compiler still generates:
class RuleOfFive {
public:
RuleOfFive() = default;
~RuleOfFive() { delete[] data_; } // 1. destructor
RuleOfFive(const RuleOfFive& other) // 2. copy constructor
: size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}
RuleOfFive& operator=(const RuleOfFive& other) { // 3. copy assignment
if (this == &other) return *this;
delete[] data_;
size_ = other.size_;
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
return *this;
}
RuleOfFive(RuleOfFive&& other) noexcept // 4. move constructor
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr;
}
RuleOfFive& operator=(RuleOfFive&& other) noexcept { // 5. move assignment
if (this == &other) return *this;
delete[] data_;
size_ = other.size_;
data_ = other.data_;
other.size_ = 0;
other.data_ = nullptr;
return *this;
}
private:
std::size_t size_ = 0;
int* data_ = nullptr;
};
Declaring a destructor that manages a raw resource, as above, is exactly when the rule of five applies — if
RuleOfFive didn’t declare a destructor at all, none of the other four would need declaring either. The
rule of zero is the preferred outcome: own resources through std::unique_ptr/std::vector/std::string
instead of raw pointers, so none of the six special members need writing — the compiler-generated ones
already do the right thing by delegating to the members:
#include <vector>
#include <memory>
class RuleOfZero {
public:
// no destructor, no copy/move ctors/assignment declared -- the compiler-generated ones
// correctly copy/move buffer_ and owned_, because vector/unique_ptr already implement rule of five themselves
private:
std::vector<int> buffer_;
std::unique_ptr<int> owned_;
};
Which of the six special members the compiler implicitly declares (and whether as defaulted or deleted) follows a fixed decision procedure driven by what a class explicitly declares itself:
destructor, copy ctor/assign,
move ctor/assign"] C["Class declares a destructor
or any copy/move member"] --> D{"Which one(s)?"} D -->|"destructor only"| E["Copy ctor/assign still generated
(deprecated), move NOT generated
-- falls back to copy"] D -->|"copy ctor or copy assign"| F["Move ctor/assign NOT generated
-- falls back to copy"] D -->|"move ctor or move assign"| G["Copy ctor/assign implicitly DELETED
-- copying becomes a compile error"] H["Member/base is not copyable
(e.g. holds unique_ptr)"] --> I["Copy ctor/assign implicitly DELETED"]
explicit
Covered in depth in Operator Overloading and Conversions; on a constructor it blocks that constructor from being used for an implicit conversion:
class Meters {
public:
explicit Meters(double value) : value_(value) {}
double value_;
};
void printDistance(Meters m);
// printDistance(5.0); // error: no implicit double -> Meters conversion
printDistance(Meters(5.0)); // fine: explicit construction
static Members
class Counter {
public:
Counter() { ++instanceCount_; }
~Counter() { --instanceCount_; }
static int instanceCount() { return instanceCount_; } // static member function: no "this", callable
// without an instance
private:
static inline int instanceCount_ = 0; // inline (C++17): definable in the class body, no separate .cpp needed
};
friend
A friend grants another function or class access to this class’s private members — an escape hatch to use
sparingly, since it breaks encapsulation between the two types:
class Box {
public:
explicit Box(int volume) : volume_(volume) {}
friend bool sameVolume(const Box& a, const Box& b); // can reach a.volume_/b.volume_ directly
private:
int volume_;
};
bool sameVolume(const Box& a, const Box& b) { return a.volume_ == b.volume_; }
const/mutable Correctness and this
A member function marked const promises not to modify the object (the compiler enforces it) and can be called
on const objects/references; mutable opts a specific member out of that promise, for state that is an
implementation detail rather than part of the object’s logical value (a cache, a mutex):
#include <string>
class Circle {
public:
explicit Circle(double radius) : radius_(radius) {}
double area() const { // const: does not modify *this
if (!areaCached_) {
cachedArea_ = 3.14159265358979 * radius_ * radius_; // allowed: cachedArea_/areaCached_ are mutable
areaCached_ = true;
}
return cachedArea_;
}
Circle& scale(double factor) { // non-const: modifies *this, returns *this for chaining
radius_ *= factor;
areaCached_ = false;
return *this; // "this" is Circle* here; *this dereferences it
}
private:
double radius_;
mutable double cachedArea_ = 0.0;
mutable bool areaCached_ = false;
};
See Also
-
C: Structures, Unions and Type Aliases — the same layout and alignment rules, without constructors, destructors, access control, or member functions.