Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Class-Level Functions
Define functions that operate on class-level data without an object instance.
Static Member Functions
In the previous lesson on Static member variables, you learned that static member variables are member variables that belong to the class rather than objects of the class. If a static member variable is public, it can be accessed directly using the class name and the scope resolution operator:
#include <iostream>
class Settings
{
public:
static inline int s_volume{ 50 };
};
int main()
{
std::cout << Settings::s_volume; // s_volume is public, we can access it directly
}
But what if a static member variable is private? Consider the following example:
#include <iostream>
class Settings
{
private: // now private
static inline int s_volume{ 50 };
};
int main()
{
std::cout << Settings::s_volume; // error: s_volume is private and can't be accessed directly outside the class
}
In this case, we can't access Settings::s_volume directly from main() because it is private. Normally we access private members through public member functions. While we could create a normal public member function to access s_volume, we'd then need to instantiate an object of the class type to use the function!
#include <iostream>
class Settings
{
private:
static inline int s_volume{ 50 };
public:
int getVolume() { return s_volume; }
};
int main()
{
Settings settings{};
std::cout << settings.getVolume(); // works, but requires us to instantiate an object to call getVolume()
}
We can do better.
Static member functions
Member variables aren't the only type of member that can be made static. Member functions can be made static as well. Here is the above example with a static member function accessor:
#include <iostream>
class Settings
{
private:
static inline int s_volume{ 50 };
public:
static int getVolume() { return s_volume; } // static member function
};
int main()
{
std::cout << Settings::getVolume() << '\n';
}
Because static member functions are not associated with a particular object, they can be called directly by using the class name and the scope resolution operator (e.g., Settings::getVolume()). Like static member variables, they can also be called through objects of the class type, though this is not recommended.
Static member functions have no this pointer
Static member functions have two interesting quirks worth noting. First, because static member functions are not attached to an object, they have no this pointer! This makes sense when you think about it—the this pointer always points to the object that the member function is working on. Static member functions do not work on an object, so the this pointer is not needed.
Second, static member functions can directly access other static members (variables or functions), but not non-static members. This is because non-static members must belong to a class object, and static member functions have no class object to work with!
Static members defined outside the class definition
Static member functions can also be defined outside of the class declaration. This works the same way as for normal member functions.
#include <iostream>
class SequenceGenerator
{
private:
static inline int s_nextValue{ 100 };
public:
static int getNextValue(); // Here's the declaration for a static function
};
// Here's the definition of the static function outside of the class. Note we don't use the static keyword here.
int SequenceGenerator::getNextValue() { return s_nextValue++; }
int main()
{
for (int count{ 0 }; count < 5; ++count)
std::cout << "The next value is: " << SequenceGenerator::getNextValue() << '\n';
return 0;
}
This program prints:
The next value is: 100
The next value is: 101
The next value is: 102
The next value is: 103
The next value is: 104
Note that because all the data and functions in this class are static, we don't need to instantiate an object of the class to make use of its functionality! This class utilizes a static member variable to hold the value of the next number to be returned, and provides a static member function to return that number and increment it.
As noted in the Classes and header files lesson, member functions defined inside the class definition are implicitly inline. Member functions defined outside the class definition are not implicitly inline, but can be made inline by using the inline keyword. Therefore a static member function that is defined in a header file should be made inline so as not to violate the One Definition Rule (ODR) if that header is then included into multiple translation units.
A word of warning about classes with all static members
Be careful when writing classes with all static members. Although such "pure static classes" (also called "monostates") can be useful, they also come with some potential downsides.
First, because all static members are instantiated only once, there is no way to have multiple copies of a pure static class (without cloning the class and renaming it). For example, if you needed two independent SequenceGenerator objects, this would not be possible with a pure static class.
Second, in the lesson on global variables, you learned that global variables are dangerous because any piece of code can change the value of the global variable and end up breaking another piece of seemingly unrelated code. The same holds true for pure static classes. Because all of the members belong to the class (instead of objects of the class), and class declarations usually have global scope, a pure static class is essentially the equivalent of declaring functions and global variables in a globally accessible namespace, with all the requisite downsides that global variables have.
Instead of writing a class with all static members, consider writing a normal class and instantiating a global instance of it (global variables have static duration). That way the global instance can be used when appropriate, but local instances can still be instantiated if and when that is useful.
Pure static classes vs namespaces
Pure static classes have a lot of overlap with namespaces. Both allow you to define variables with static duration and functions within their scope region. However, one significant difference is that classes have access controls while namespaces do not.
In general, a static class is preferable when you have static data members and/or need access controls. Otherwise, prefer a namespace.
C++ does not support static constructors
If you can initialize normal member variables via a constructor, then by extension it makes sense that you should be able to initialize static member variables via a static constructor. And while some modern languages do support static constructors for precisely this purpose, C++ is unfortunately not one of them.
If your static variable can be directly initialized, no constructor is needed: you can initialize the static member variable at the point of definition (even if it is private). We do this in the SequenceGenerator example above. Here's another example:
#include <iostream>
struct Vowels
{
char first{};
char second{};
char third{};
char fourth{};
char fifth{};
};
struct TextData
{
static inline Vowels s_vowels{ 'a', 'e', 'i', 'o', 'u' }; // initialize static variable at point of definition
};
int main()
{
std::cout << TextData::s_vowels.third; // print i
return 0;
}
If initializing your static member variable requires executing code (e.g., a loop), there are many different, somewhat obtuse ways of doing this. One way that works with all variables, static or not, is to use a function to create an object, fill it with data, and return it to the caller. This returned value can be copied into the object being initialized.
#include <iostream>
struct Vowels
{
char first{};
char second{};
char third{};
char fourth{};
char fifth{};
};
class TextData
{
private:
static Vowels generate()
{
Vowels v{}; // create an object
v.first = 'a'; // fill it with values however you like
v.second = 'e';
v.third = 'i';
v.fourth = 'o';
v.fifth = 'u';
return v; // return the object
}
public:
static inline Vowels s_vowels{ generate() }; // copy the returned object into s_vowels
};
int main()
{
std::cout << TextData::s_vowels.third; // print i
return 0;
}
Related content
A lambda can also be used for this.
We show a practical example of this methodology in the Global random numbers (Random.h) lesson (though we do it with a namespace rather than a static class, it works the same way).
Summary
Static member functions: Member functions that belong to the class rather than to individual objects. They can be called using the class name and scope resolution operator without needing to instantiate an object.
No this pointer: Static member functions do not have a this pointer because they are not associated with any particular object. This means they can only access static members directly.
Access control: Static member functions can access both static member variables and other static member functions, but cannot directly access non-static members since those require an object.
Defining outside the class: Static member functions can be defined outside the class definition, similar to regular member functions. When defined in a header file, they should be marked inline to avoid ODR violations.
Pure static classes: Classes with all static members (monostates) can be useful but have downsides. They cannot be instantiated multiple times, and they share the same issues as global variables. Consider using normal classes with global instances instead for more flexibility.
Static classes vs namespaces: Pure static classes and namespaces have significant overlap. Classes provide access controls, while namespaces do not. Generally, prefer static classes when you need access controls or have static data members; otherwise, use namespaces.
No static constructors: C++ does not support static constructors. For complex initialization, use helper functions that return initialized objects, or initialize static members at their point of definition.
Static member functions provide a way to implement class-level functionality that doesn't depend on individual objects. They are useful for utility functions, factory methods, and managing static member variables while maintaining encapsulation.
Class-Level Functions - Quiz
Test your understanding of the lesson.
Practice Exercises
ID Generator with Static Members
Create a class that uses static members to generate unique IDs. Implement a static member function to access the static data.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!