A class is a type with members. It is introduced with one of three class keys,
class, struct or union [class]. Inheritance is
on the Inheritance page.
Declaration
class-specifier:
class-head { [member-specification] }
class-head:
class-key [attribute-specifier-seq] class-head-name [class-virt-specifier] [base-clause]
class-key [attribute-specifier-seq] [base-clause]
class-key: class | struct | union
class-virt-specifier: final
class | struct | union | |
|---|---|---|---|
| Default member access | private | public | public |
| Default base access | private | public | Unions have no bases |
| Storage of data members | All present | All present | One active at a time |
struct and class are otherwise the same. A class declared
final (C++11) cannot be a base class.
class Account {
public:
explicit Account(int id) : id_(id) { }
int balance() const { return balance_; } // does not change the object
void deposit(int amount);
static int count(); // no this pointer
private:
int id_;
int balance_ = 0; // default member initialiser (C++11)
inline static int next_id_ = 1; // inline static member (C++17)
};
void Account::deposit(int amount) // defined outside the class
{
balance_ += amount;
}
Members
| Member kind | Notes | Section |
|---|---|---|
| Non-static data member | A part of each object. May have a default initialiser | [class.mem] |
| Static data member | One per program, not per object. Needs a definition outside the class unless inline or constexpr | [class.static.data] |
| Non-static member function | Called on an object, and has this | [class.mfct.non.static] |
| Static member function | Has no this, and may not be virtual | [class.static.mfct] |
| Nested class | A class declared inside another. It has access to the enclosing class’s names | [class.nest] |
| Bit-field | A member with a width in bits: int flag : 3; | [class.bit] |
| Member type, alias, enumeration | Named as C::name | [class.mem] |
| Using-declaration, friend, static_assert | Members or other declarations in the class body | [class.mem] |
The this pointer and qualifiers
Inside a non-static member function, this is a pointer to the object
[expr.prim.this]. Its type reflects the function’s qualifiers.
| Qualifier | Meaning |
|---|---|
const | The function does not modify non-mutable members, and can be called on a const object |
volatile | Can be called on a volatile object |
& | Can be called only on an lvalue (C++11) |
&& | Can be called only on an rvalue (C++11) |
noexcept | The function does not throw; see Exceptions |
A function with an explicit object parameter (C++23), declared with
this on its first parameter, receives the object as an ordinary argument.
This allows a single function to be written for all value categories, a technique called
“deducing this”. Inside, it has no this pointer, so members need the
parameter name.
class Box {
std::string value_;
public:
const std::string& get() const & { return value_; }
std::string get() && { return std::move(value_); }
// C++23: one definition for every kind of object
template<class Self>
auto&& name(this Self&& self) { return std::forward<Self>(self).value_; }
};
mutable
A data member declared mutable may be changed through a
const object [dcl.stc]. It suits caches and locks that do not change the
visible state of the object. A lambda declared mutable may change its
copied captures.
Unions
A union holds one active member at a time, and all members share the same storage
[class.union]. Reading a member other than the active one is undefined behaviour
except for a common initial sequence of standard-layout members. Since C++11 a union
can have members with non-trivial special functions, though it must then manage them by
hand. An anonymous union makes its members part of the enclosing scope.
std::variant (C++17) is the type-safe alternative.
Layout
Data members are laid out in declaration order, with higher addresses for later
members; C++23 requires this for all members whatever their access [class.mem].
Compilers may add padding for alignment. An empty class has a size of at least 1, but as
a base class may occupy no space. [[no_unique_address]] (C++20) lets an
empty member do the same.
Access control
| Specifier | Accessible from |
|---|---|
public | Anywhere |
protected | The class, its friends, and classes derived from it, subject to [class.protected] |
private | The class and its friends |
Access is checked after name lookup and overload resolution [class.access], so a private overload can be chosen, and the call then fails. Access specifiers apply until the next one. Access applies to names, not to objects: a member function can use the private members of any object of the same class.
Friends
A friend declaration gives a function or class access to the private and
protected members of the class [class.friend]. Friendship is not inherited or
transitive, and is not symmetric.
class Vec {
int x_, y_;
friend bool operator==(const Vec& a, const Vec& b) // hidden friend
{
return a.x_ == b.x_ && a.y_ == b.y_;
}
friend class Serialiser; // a whole class
};
Special member functions
Six member functions are special, because the compiler can write them for you [special].
| Function | Form | Section |
|---|---|---|
| Default constructor | C() | [class.default.ctor] |
| Copy constructor | C(const C&) | [class.copy.ctor] |
| Move constructor (C++11) | C(C&&) | [class.copy.ctor] |
| Copy assignment | C& operator=(const C&) | [class.copy.assign] |
| Move assignment (C++11) | C& operator=(C&&) | [class.copy.assign] |
| Destructor | ~C() | [class.dtor] |
What you declare decides what the compiler declares for you. This table is for the usual cases; “deprecated” means the function is still generated, but relying on it is deprecated.
| You declare | Default ctor | Destructor | Copy ctor | Copy assign | Move ctor | Move assign |
|---|---|---|---|---|---|---|
| Nothing | Generated | Generated | Generated | Generated | Generated | Generated |
| Any other constructor | Not declared | Generated | Generated | Generated | Generated | Generated |
| Destructor | Generated | Yours | Generated, deprecated | Generated, deprecated | Not declared | Not declared |
| Copy constructor | Not declared | Generated | Yours | Generated, deprecated | Not declared | Not declared |
| Copy assignment | Generated | Generated | Generated, deprecated | Yours | Not declared | Not declared |
| Move constructor | Not declared | Generated | Deleted | Deleted | Yours | Not declared |
| Move assignment | Generated | Generated | Deleted | Deleted | Not declared | Yours |
An implicit member is defined as deleted if it cannot be generated, for example when a member has no accessible copy constructor. A common guideline is the rule of zero: if a class’s members manage their own resources, declare none of these. The rule of five: if you declare any of the copy, move or destructor functions, declare all five.
= default asks for the compiler’s version, and = delete
forbids a function and removes it from use [dcl.fct.def.default],
[dcl.fct.def.delete]. Deleted functions still take part in overload resolution.
Constructors
ctor-initializer: : mem-initializer-list
mem-initializer: mem-initializer-id ( [expression-list] )
mem-initializer-id braced-init-list
A constructor has no return type and the same name as the class [class.ctor]. It
cannot be virtual, static or const.
The order of initialisation is fixed [class.base.init], whatever order the member initialiser list uses:
- Virtual base classes, in depth-first left-to-right order, and only by the most derived class.
- Direct base classes, in the order of the base-clause.
- Non-static data members, in declaration order.
- The constructor body.
A member initialiser list takes priority over a default member initialiser for the same member.
class Buffer {
std::size_t size_;
char* data_;
public:
explicit Buffer(std::size_t n) // explicit: no implicit conversion
: size_(n), data_(new char[n]) { }
Buffer() : Buffer(1024) { } // delegating constructor (C++11)
Buffer(const Buffer& o) // copy
: size_(o.size_), data_(new char[o.size_])
{ std::copy_n(o.data_, size_, data_); }
Buffer(Buffer&& o) noexcept // move: steals the pointer
: size_(std::exchange(o.size_, 0)),
data_(std::exchange(o.data_, nullptr)) { }
~Buffer() { delete[] data_; }
};
noexcept so containers will use them- A delegating constructor (C++11) names another constructor of the same class in its initialiser list, and has no other initialisers.
using Base::Base;(C++11) inherits constructors [class.inhctor.init]. See Inheritance.- A converting constructor takes one argument, and is usable for implicit conversion unless
explicit[class.conv.ctor].explicit(condition)(C++20) makes this conditional. - A conversion function converts the other way:
operator int() const;[class.conv.fct]. It can also beexplicit. - If a constructor throws, the fully built bases and members are destroyed; see Exceptions.
Copy elision
Since C++17, a prvalue used to initialise an object of the same type constructs that object directly, and no copy or move is made, even if there is no copy or move constructor [class.copy.elision], [dcl.init].
Widget make() { return Widget{}; } // mandatory: no copy or move
Widget w = make(); // constructs w directly
Widget named()
{
Widget local;
return local; // named return value: elision is allowed but optional
}
Returning a named local variable is the optional case, known as named return value optimisation. If it is not elided, the local is treated as an rvalue (implicit move), so the move constructor is used if one is available. C++23 simplified these rules.
Destructors
A destructor has the form ~C(), takes no arguments and returns nothing
[class.dtor]. It is implicitly noexcept unless declared otherwise or a
base or member has a potentially-throwing destructor. Objects are destroyed in reverse
order of construction: the body runs, then non-static data members in reverse order
of declaration, then direct bases in reverse order, then virtual bases if this
is the most derived class.
A destructor can be called explicitly: p->~C();. It is
usually paired with placement new. Destroying a base
class object through a base pointer needs a
virtual destructor.
Operator overloading
An operator can be given a meaning for class or enumeration operands [over.oper]. It is
defined as a function called operator followed by the symbol. The number of
operands and the precedence are fixed.
| Can be overloaded |
|---|
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= <=> && || ++ -- , ->* -> () [] co_await |
| Cannot be overloaded |
|---|
:: . .* ?: sizeof alignof typeid and the cast operators |
operator=,operator(),operator[]andoperator->must be non-static member functions, with exceptions in C++23 foroperator()andoperator[].- Overloaded
&&and||evaluate both operands, so short-circuiting is lost [over.oper]. operator++()is prefix;operator++(int)is postfix, and theintis a marker.operator[]takes any number of arguments since C++23:m[1, 2].operator()andoperator[]may bestaticsince C++23.operator newandoperator deletecan be overloaded as allocation functions; see Expressions.- A literal operator is written
operator""_suffix.
Three-way comparison
operator<=> (C++20), the “spaceship” operator, compares two values
and returns an ordering [class.compare]. Writing it as defaulted also defines
==.
#include <compare>
struct Version {
int major, minor, patch;
auto operator<=>(const Version&) const = default; // compares in member order
};
bool newer = Version{2, 1, 0} > Version{2, 0, 9}; // true
| Category | Meaning | Example |
|---|---|---|
std::strong_ordering | Equal values are substitutable | Integers |
std::weak_ordering | Equivalent values may still differ | Case-insensitive strings |
std::partial_ordering | Some values are unordered | Floating-point numbers, because of NaN |
From a single operator, the compiler rewrites the other comparisons [over.match.oper]:
a < b becomes (a <=> b) < 0, a != b
becomes !(a == b), and the arguments may be swapped.