Static Class Variables and Functions

A guide to sharing values between objects using static class variables and functions
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

373.jpg
Ryan McCombe
Ryan McCombe
Edited

Previously, we've talked about how classes are blueprints for creating objects. These objects, or instances, will all have the same structure. They have the same variables and the same functions that can access and update those variables.

Each individual object gets its own copy of the class variables. As such, different objects can have different values for each variable:

#include <string>

class Vampire {
public:
  int Health{100};
  std::string Faction{"Undead"};
};

int main(){
  Vampire A;
  A.Health = 50;
  
  Vampire B;
  B.Health = 200;
}

In the above example, all instances of Vampire will have a Health and Level value, and those values can be different for each object.

But what if we don’t want this behavior? What if we want the Faction of all Vampire objects to remain the same?

In this lesson, we’ll introduce the concept of static class variables, which achieves exactly this.

Static Class Variables

Whilst a variable like Health can vary from object to object, all of the objects of this class should share the same Faction

We can do this by marking the Faction variable as static and inline:

#include <string>

class Vampire {
public:
  int Health{100};
  static inline std::string Faction{"Undead"};
};

Why do we need inline?

C++ assumes a class definition is going to be included in multiple files. This is a reasonable assumption as, in a real program, it’s almost always true.

Static class variables are part of the class, rather than as part of objects of that class. Therefore, when a class declaration defines a static variable, that is very similar to defining a global variable. Static class members, such as the Vampire::Faction symbol, will have external/global linkage.

Once a header containing a static class variable definition is included in multiple other files, each of those files would be defining that symbol.

This breaches the one definition rule (ODR), so the compiler pre-emptively asks us to mark in-class static declarations as inline.

For variables, keeping the definition and declaration in the same place and marking it as inline is generally recommended. But, as an alternative, we can move the definition to the cpp file. Assuming we’re not #includeing cpp files into other files, this removes the need for inline:

// Vampire.h
#pragma once
#include <string>

class Vampire {
public:
  // Declaration
  static std::string Faction;
};
// Vampire.cpp
#include "Vampire.h"

// Definition
std::string Vampire::Faction{"Undead"};

Static variables are shared across all instances of a class. In other words, any time we access the Faction property of one of our Vampire objects, we are accessing the same location in memory.

We can verify this by checking what is returned by &, the address-of operator:

#include <iostream>
#include <string>

class Vampire {
public:
  static inline std::string Faction{"Undead"};
};

int main(){
  Vampire A;
  Vampire B;
  if (&A.Faction == &B.Faction) {
    std::cout << "They're the same";
  }
}
They're the same

Updating Static Variables

Given that all instances of a class share the same static variable, updating it on one object will have the effect of updating it on all objects of that class:

#include <iostream>
#include <string>

class Vampire {
public:
  static inline std::string Faction{"Undead"};
};

int main(){
  Vampire A;
  Vampire B;

  // Setting A.Faction
  A.Faction = "Demonic";

  // Getting B.Faction
  std::cout << B.Faction;
}
Demonic

We can prevent a static variable from being updated in the usual ways, by marking it as a constant:

#include <string>

class Vampire {
public:
  const static inline std::string Faction{
    "Undead"};
};

int main(){
  Vampire A;

  // Cannot update a const
  A.Faction = "Demonic";
}

constexpr Static Variables

Another implication of a class variable being static is that it can also be marked as a compile-time constant, using the constexpr keyword.

A constexpr variable is implicitly inline, so we can simultaneously remove the inline specifier:

#include <string>

class Vampire {
public:
  static constexpr std::string Faction
    {"Undead"};
};

It might seem like Faction could have been a compile-time constant even before it was marked as static. After all, the value of "Undead" was known at compile time.

However, before we marked it as static, the variable itself did not exist at compile time. Because it was a regular class variable, it is only created when objects of that class are created. For this class, that can only happen at run time.

However, we can consider static variables as being part of the class, rather than part of the object. The class is known at compile time, so static class variables can be constexpr.

Accessing Static Variables from the Class

Aside from these memory savings and the fact that our code now makes our intentions explicit, there is a further benefit to using static when appropriate.

Due to the fact every static class member is shared across all instances of a class, we can find out what that value is without needing to create an object.

We can access a static class member directly from the class, using the scope resolution operator ::

#include <iostream>
#include <string>

class Vampire {
public:
  static inline std::string Faction{"Undead"};
};

int main(){
  std::cout << "All Vampires are " <<
    Vampire::Faction;

  Vampire::Faction = "Demonic";
  std::cout << "\nThey're now " <<
    Vampire::Faction;
}
All Vampires are Undead
They're now Demonic

static Functions

We've seen how classes can have const functions, which do not write to the properties of the object on which those functions are called.

We can also have static functions. Static functions do not read nor write to the properties of the object, unless those properties are also static.

#include <string>

class Vampire {
public:
  static std::string GetFaction(){
    return Faction;
  }

private:
  static inline std::string Faction{"Undead"};
};

Static class functions can call other static functions. Attempting to call a non-static class function from a static class function will result in a compilation error.

Similar to static variables, static functions can be called from an object of the class using the . operator.

But they can also be called from the class itself, using the :: operator:

#include <iostream>
#include <string>

class Vampire {
public:
  static std::string GetFaction(){
    return Faction;
  }

private:
  static inline std::string Faction{"Undead"};
};

int main(){
  std::cout << "All Vampires are "
    << Vampire::GetFaction();
}
All Vampires are Undead

Static Classes vs Namespaces

Accessing static class members through the :: operator bears some similarities to how we access members of a namespace.

To an extent, static members of a class can accomplish similar goals as namespaces, and the concept of a “static class” (a class that contains only static members) is a common pattern in other programming languages.

However, it tends to be less useful in C++. Namespaces achieve the goal in a simpler, more direct way. For the most part, we should only create classes (or structs) if we plan to instantiate objects from them.

But, sometimes a class really is the most intuitive place to put a variable or a function, even if won’t change from object to object. So, for those scenarios, we can just add the code to our class, and mark it as static.

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Edited
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Friend Classes and Functions

An introduction to the friend keyword, which allows classes to give other objects and functions enhanced access to its members
DreamShaper_v7_nurse_Sidecut_hair_modest_clothes_fully_clothed_2.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved