10 - static methods

Just like static fields, static methods are not tied to any particular instance of the class. They are essentially global (free) functions in the class namespace.

  • static methods can not access non-static class fields.

  • static methods can not have any qualifiers (const, volatile, &, &&). Since they do not work on any object, there is no point in declaring any object-specific behavior.

Named constructors

One particularly good use of static methods is named constructor idiom. In some languages, constructors can have specific names. In C++ and many other languages that support OOP, constructors have no names - they just use the name of the class. This can sometimes lead to unclear code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class temperature
{
public:
	temperature(double value)
	: value(value) {}

	// other methods...

private:
	double value;
};

void some_func()
{
	// What unit is this?
	// Celsius? Fahrenheit? Kelvin?
	temperature t = 0.0;
}

Clarity of such code can be improved by implementing named constructors (or actually faking it since C++ has no such feature):

  • make the constructor private

  • supply public static methods that return an object

Now any external code that uses the class is much clearer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
constexpr double celsius_to_fahrenheit(double celsius)
{
	return 1.8 * celsius + 32;
}

constexpr double fahrenheit_to_celsius(double fahrenheit)
{
	return (fahrenheit - 32) / 1.8;
}

// https://en.wikipedia.org/wiki/Absolute_zero
constexpr double absolute_zero = -273.15;

constexpr double celsius_to_kelvin(double celsius)
{
	return celsius - absolute_zero;
}

constexpr double kelvin_to_celsius(double kelvin)
{
	return kelvin + absolute_zero;
}

class temperature
{
public:
	static temperature from_kelvin(double kelvin)
	{
		return temperature(kelvin);
	}

	static temperature from_celsius(double celsius)
	{
		return from_kelvin(celsius_to_kelvin(celsius));
	}

	static temperature from_fahrenheit(double fahrenheit)
	{
		return from_celsius(fahrenheit_to_celsius(fahrenheit));
	}

	// other methods...

private:
	temperature(double kelvin)
	: kelvin(kelvin) {}

	double kelvin;
};

void some_func()
{
	// now any object creation is very clear
	auto t1 = temperature::from_kelvin(0);
	auto t2 = temperature::from_celsius(100);
}

Singletons

A common pattern that uses static methods and private constructor is singleton, that is a class that is designed on purpose to only allow one object of its type.

This can sometimes be useful to limit interactions with the state that should exist only once such as program-wide configuration object, but the pattern can very quickly become an antipattern if overused. The more singletons a program has, the more interactions with global state (complicates reasoning about the program) and the more troublesome testing becomes. Code with lots of singletons is also much harder to refactor because one place of code can couple very arbitrary, different parts of the program, trigerring a cascade of required changes if the singleton is to be modified.

Canonical singleton in C++ looks as follows (this is sometimes called Meyer's Singleton as this form appeared in his book):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class singleton
{
public:
	singleton& get_instance()
	{
		static singleton instance(/* args... */);
		return instance;
	}

	// remove every other possibility of object creation
	// copy/move ctor/operator= are explained in RAII chapter
	singleton(const singleton&) = delete;
	singleton& operator=(const singleton&) = delete;
	singleton(singleton&&) = delete;
	singleton& operator=(singleton&&) = delete;

	// public functions that offer actual functionality...

private:
	singleton(/* ctor arguments... */);

	// state...
};

Empty classes with static methods

Sometimes you may encounter a code like this:

1
2
3
4
5
6
7
8
9
10
class math
{
public:
	static double sin(double rad);
	static double cos(double rad);
	// more trigonometry...
	// all metods are static

	// no data members
};

Such code is against C++ idioms. It's typically written by people who are too used to OOP and don't understand that in C++ there is no need to create a class. Some languages force OOP style and (probably the most disliked language by C++ programmers) Java is a primary example (hello world in Java requires to write a class).

The correct code for the example above is:

1
2
3
4
5
6
namespace math
{
	double sin(double rad);
	double cos(double rad);
	// more trigonometry...
}

Notice that after refactoring, function calls would remain the same: math::sin(angle) etc. If the class had any private methods as implementation detail, they can be moved to an anonymous namespace.

Corner cases

Pretty much no one writes code like this so it's more of a fun fact than any rule worth remembering.

Why ban static data members in these cases? I don't really know, my guess is that it simplifies the job of compiler writers.

Why are such definitions even possible? Perhaps some consistency reason and maybe backwards compatibility, but I remember that once being able to define an enum inside a function was useful. Note that lambda expressions technically each create a new type and by design they are intended to be defined locally.