Ch13.9: Static Member Functions
1. A Member Function Without an Object
A static member function is a member function that is not
called on an object. It has no this
(Ch13.7), it is called with the class
name instead of a variable name, and it can be called even when no object of
the class exists:
::std::size_t n{account::opened()}; // no object involved
Everything else is unchanged: it is inside the class, so it obeys the same access rules — it may read and write private members, including the private constructors. That combination, “outside the object, inside the wall”, is what makes it useful.
2. Static Member Functions Cannot Be const
const on a member function is a statement about this,
and a static member function has no this, so the two cannot be
combined:
error: static member function 'static int_least32_t counter::instances()'
cannot have cv-qualifier
It also cannot name a non-static data member, for the same reason: there is no object, so “the member of this object” has no meaning. Static members may of course be used — those belong to the class, which is exactly what a static member function is attached to.
3. The Main Use: A Named Constructor
A constructor has one name: the name of the class. If a class can be built in
several ways with several different meanings, constructors stop being
expressive: account{100} and account{100, 5} do not
say what the numbers are. A static member function gives the construction a
name, and lets you do work before the object exists:
#include <fast_io.h>
#include <cstdint>
#include <cstddef>
using namespace ::fast_io::io;
class account
{
public:
// A named constructor: the only way to make an account.
static account open(::std::int_least64_t opening) throws
{
if (opening < 0)
{
throw throws ::std::errc::invalid_argument;
}
return account{opening};
}
// No object needed: it reports on the class as a whole.
static ::std::size_t opened() noexcept
{
return next_id_ - 1;
}
::std::int_least64_t balance() const noexcept
{
return balance_;
}
::std::size_t id() const noexcept
{
return id_;
}
private:
explicit account(::std::int_least64_t opening) noexcept
: balance_{opening}, id_{next_id_++}
{
}
::std::int_least64_t balance_{};
::std::size_t id_{};
inline static ::std::size_t next_id_{1};
};
int main()
{
try
{
auto a{account::open(100)};
auto b{account::open(250)};
println("a: id = ", a.id(), ", balance = ", a.balance());
println("b: id = ", b.id(), ", balance = ", b.balance());
println("accounts opened: ", account::opened());
auto bad{account::open(-5)};
println("never reached: ", bad.balance());
}
catch throws (::std::error e)
{
print("account::open failed: ", e, "\n");
}
}
a: id = 1, balance = 100
b: id = 2, balance = 250
accounts opened: 2
account::open failed: [posix](22)Invalid argument
-
The constructor is private.
account’s real constructor isprivate, soaccount{100}outside the class does not compile.openis the only door, and it is a door that checks. -
The name says what is happening.
account::open(100)reads as an action on the class, not as a puzzle about which two numbers went where. -
The check happens once, before the object exists. A failed
open(-5)throws before an account is constructed, so there is no moment where a half-built object exists and no “is this object valid?” flag anywhere. This is the same guarantee as a validating constructor (Ch13.5), with a better name. -
Several named constructors can coexist without overloading
tricks:
open(...),open_with_overdraft(...),from_history(...). Each name tells the reader which question it answers.
4. When to Write One
| Use a static member function | Use an ordinary member function |
|---|---|
| The operation is about the class as a whole: how many exist, what the limits are, how to build one from some other representation. | The operation is about one object: deposit, withdraw, balance. |
| You need to validate or transform the arguments before constructing an object. | The object already exists and you are changing or reading it. |
| You must reach private members but have no object to reach them through. | You have the object — that is the normal case. |
friend
(Ch13.10) is the other way to allow it
without making the data public.
public: it is a name you have promised to keep,
exactly like a public member function. Put it there only if callers outside the
class really need it; a validation helper that only open uses
belongs after private:.