C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
55: Class Templates
I've spent a lot of my career cleaning up "copy-paste" debt. You know the pattern: you write a class to handle a specific task for integers, then you realize you need the exact same logic for doubles. So, you copy the file, rename it DoubleSomething, and change a few type declarations. Then you need it for strings. By the time you're at the fourth or fifth version, you're maintaining five nearly identical files. It's a nightmare to debug because if you find a logic error in one, you have to remember to fix it in all the others.
This is where class templates come in. Instead of writing a class for a specific type, we write a blueprint that tells the compiler, "I don't know what the type is yet, but whatever it ends up being, here is how the class should behave."
Stopping the Copy-Paste Cycle
Let's imagine we're building a small game engine. We need a Property class that holds a value and a name (like "Health" or "PlayerName"). We want to be able to get and set these values, but "Health" is an integer and "PlayerName" is a string. I'm not going to write two separate classes for this.
template <typename T>
class Property {
private:
std::string name;
T value;
public:
Property(std::string n, T v) : name(n), value(v) {}
T getValue() const { return value; }
void setValue(T newValue) { value = newValue; }
std::string getName() const { return name; }
};
That template <typename T> line is the magic. I'm telling C++ that T is a placeholder. When I eventually create an object of this class, I'll tell the compiler what T should actually be.
The "Wait, This Doesn't Compile" Moment
Now, here is where I usually trip up when I'm moving too fast. I decided I wanted a reset() method that sets the value back to a "default" state. In my head, I was thinking about numbers, so I wrote this:
template <typename T>
class Property {
// ... (previous code) ...
void reset() {
value = 0;
}
};
This looks fine, right? It compiles perfectly as long as I only use Property<int>. But the moment I try to instantiate a Property<std::string>, the compiler screams at me. Why? Because I can't assign an integer 0 to a std::string. I forgot that T could be anything.
The fix is to use the type T itself to handle the reset, rather than assuming it's a number. In C++, T() calls the default constructor for whatever type T is. For an int, that's 0; for a string, that's an empty string. Much better.
void reset() {
value = T();
}
Putting the Blueprint to Work
Now that we have a flexible blueprint, using it is straightforward. We just provide the type in angle brackets during instantiation. I like to think of this as "specializing" the class for a specific use case.
int main() {
// A property for health (integer)
Property<int> health("Health", 100);
health.setValue(80);
// A property for the player's name (string)
Property<std::string> playerName("PlayerName", "Aragorn");
playerName.setValue("Strider");
health.reset(); // Sets health to 0
playerName.reset(); // Sets name to ""
return 0;
}
The cool part here is that the compiler generates two distinct classes behind the scenes: one where T is int and one where T is std::string. You get the type safety of a strictly typed language without the tediousness of writing the same code over and over.
📋 Practical Task
Build a Generic "TimedValue" Wrapper
To practice class templates, you are going to build a utility class called TimedValue. This class is common in networking or sensor systems where you need to know not just the current value of something, but also when it was last updated.
Your task is to implement a class template TimedValue<T> with the following requirements:
- Private Members: A variable of type
Tto hold the value, and along long(orstd::chrono::steady_clock::time_point) to hold the timestamp. - Constructor: A constructor that initializes both the value and the timestamp.
- Update Method: A method called
update(T newValue)that updates the value and automatically refreshes the timestamp to the current time. - Getter: A method to retrieve the current value.
Once implemented, instantiate the class twice in your main() function: once for a double (e.g., representing a temperature sensor) and once for a bool (e.g., representing a door's open/closed status). Update both values and print them to the console to verify it works.
There are no comments for now.