Some pieces of data belong to a class rather than to any individual object of that class. For this purpose, we create static data members. A static data member is a single copy of such data that can be viewed by all objects. A static member function is a member function that can be called without the use of an object and can only access static members.
Each object will have one copy of a "normal" data member, whereas a static data member will have only one storage unit regardless of the number of objects that will be created. This can be seen by examining the number of active objects.
#include <iostream>
class Tracer
{
private:
static int count;
public:
Tracer()
{
++count;
std::cout << "objects alive: " << count << '\n';
}
// Run this destructor when the object lifetime ends
~Tracer()
{
--count;
std::cout << "objects alive: " << count << '\n';
}
};
int Tracer::count{};
int main()
{
Tracer a{};
{
Tracer b{};
Tracer c{};
}
return 0;
}
The member count is declared within the class and is marked as static. Because it's static, it's not part of any object; only one int called count is declared as part of the class as a whole. The definition int Tracer::count{}; resides outside the class declaration and initializes that single variable.
As each Tracer object is constructed, the constructor increments the common counter and prints the count, while the destructor decrements it and prints once again.
The result of executing the code can be seen to reflect the life of each of the 3 objects. The count increments to 1, 2, 3, and then decrements to 2, 1, 0 as each object enters and exits scope. All three objects access and modify the identical int variable.
A static data member may be accessed either through a class object or through the class name. Both expressions use the same variable.
#include <iostream>
class Counter
{
public:
static int total;
Counter()
{
// Increment the value before the expression uses it
++total;
}
};
int Counter::total{};
int main()
{
Counter a{};
Counter b{};
std::cout << a.total << '\n';
std::cout << Counter::total << '\n';
return 0;
}
The call to a.total and the call Counter::total both print 2 after two objects have already been created. A static member function called through an object ignores that object, so using the class name to call a static member function clearly signifies that the static member function can be called from anywhere it's declared, so a class object doesn't have to be in scope.
Static member functions work on the same principles. They are declared as static and can be invoked with the use of an object, without one also. However, because they cannot know what object they might be called on, they are unable to access normal non static members and may only use static data members.
#include <iostream>
// Define the members that belong to this type
class Math
{
public:
static int square(int n)
{
return n * n;
}
};
int main()
{
std::cout << Math::square(5) << '\n';
return 0;
}
The function is static; thus it is called by typing Math::square(5). A Math object is never declared: the function receives an integer, squares it, and returns the result. Any attempt to call a non static data member from within a static member function will result in a compile time error as there isn't one particular object that it could refer to.
A static function could still be invoked using an object that was declared as an object. Thus, the two previous expressions call the static function in different ways.
#include <iostream>
class Utility
{
public:
// Keep this local value alive between function calls
static void greet()
{
std::cout << "hello\n";
}
};
int main()
{
Utility u{};
u.greet();
Utility::greet();
return 0;
}
Both the calls print hello. The call using the object u does not pass an object of type u into the static function. Static functions, in particular, do not have a this pointer and so must take the data that they use as an argument if such data isn't also static within the class.
Static data members can be seen as holding data that is relative to the entire type as opposed to relative to any individual object, e.g., a static data member could be the count of how many objects of a certain type are currently alive, or it may hold some configuration value shared by all objects. A static member function provides the opportunity to encapsulate some functionality into the class that is related to the entire type rather than individual objects, e.g., the square of a number doesn't need any specific object. The keyword static has been used both times.
In each case, the function or data now belongs to the class as a whole.
The class name becomes the natural way to access the static member and normal access rules, public/private, are protected.
Exercise: Static MembersStarter code, hints and solutionOpenClose
Practice: Static Members
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Create one additional Tracer inside the existing nested scope. Predict the maximum shared count and its changes as the nested objects leave scope.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
class Tracer
{
private:
static int count;
public:
Tracer()
{
++count;
std::cout << "objects alive: " << count << '\n';
}
// Run this destructor when the object lifetime ends
~Tracer()
{
--count;
std::cout << "objects alive: " << count << '\n';
}
};
int Tracer::count{};
int main()
{
Tracer a{};
{
Tracer b{};
Tracer c{};
}
return 0;
}Hints
Tracer::count is one shared variable, not a separate counter in a, b, and c. Each constructor increments it and each destructor decrements it.
b and c leave scope at the inner closing brace while a remains alive. Trace the shared count at that boundary before the last destructor runs.
Worked solution
One more Tracer is constructed in the inner scope. The shared static count reaches 4, then falls as inner objects are destroyed. It is one class-wide counter, not one counter per object.
#include <iostream>
class Tracer
{
private:
static int count;
public:
Tracer()
{
++count;
std::cout << "objects alive: " << count << '\n';
}
// Run this destructor when the object lifetime ends
~Tracer()
{
--count;
std::cout << "objects alive: " << count << '\n';
}
};
int Tracer::count{};
int main()
{
Tracer a{};
{
Tracer b{};
Tracer c{};
Tracer d{};
}
return 0;
}
Example output
objects alive: 1
objects alive: 2
objects alive: 3
objects alive: 4
objects alive: 3
objects alive: 2
objects alive: 1
objects alive: 0
0 comments
No comments yet. Be the first.