Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 T to hold the value, and a long long (or std::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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.