Skip to content

📦 Class Templates — Generic Classes

Class templates cho phép tạo generic containers và data structures — giống như std::vector<T>.

Cú pháp cơ bản

cpp
template<typename T>
class Box {
private:
    T value_;
    
public:
    Box(T value) : value_(value) {}
    
    T getValue() const { return value_; }
    void setValue(T value) { value_ = value; }
};

int main() {
    Box<int> intBox(42);
    Box<std::string> strBox("Hello");
    Box<double> doubleBox(3.14);
    
    std::cout << intBox.getValue() << std::endl;   // 42
    std::cout << strBox.getValue() << std::endl;   // Hello
    
    return 0;
}

Template Visualization

┌─────────────────────────────────────────────────────────────────┐
│                    CLASS TEMPLATE INSTANTIATION                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Template:        template<typename T> class Box { T value_; } │
│                                   │                             │
│                 ┌─────────────────┼─────────────────┐           │
│                 ▼                 ▼                 ▼           │
│            Box<int>         Box<double>       Box<string>       │
│            ┌───────┐        ┌─────────┐       ┌──────────┐     │
│            │int    │        │double   │       │string    │     │
│            │value_ │        │value_   │       │value_    │     │
│            └───────┘        └─────────┘       └──────────┘     │
│                                                                 │
│  3 SEPARATE classes generated at compile time!                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Complete Box<T> Example

cpp
#include <iostream>
#include <stdexcept>

template<typename T>
class Box {
private:
    T* data_;
    bool hasValue_;
    
public:
    // Default constructor
    Box() : data_(nullptr), hasValue_(false) {}
    
    // Value constructor
    explicit Box(const T& value) 
        : data_(new T(value)), hasValue_(true) {}
    
    // Copy constructor
    Box(const Box& other) 
        : data_(other.hasValue_ ? new T(*other.data_) : nullptr),
          hasValue_(other.hasValue_) {}
    
    // Move constructor
    Box(Box&& other) noexcept
        : data_(other.data_), hasValue_(other.hasValue_) {
        other.data_ = nullptr;
        other.hasValue_ = false;
    }
    
    // Destructor
    ~Box() {
        delete data_;
    }
    
    // Copy assignment
    Box& operator=(const Box& other) {
        if (this != &other) {
            delete data_;
            data_ = other.hasValue_ ? new T(*other.data_) : nullptr;
            hasValue_ = other.hasValue_;
        }
        return *this;
    }
    
    // Move assignment
    Box& operator=(Box&& other) noexcept {
        if (this != &other) {
            delete data_;
            data_ = other.data_;
            hasValue_ = other.hasValue_;
            other.data_ = nullptr;
            other.hasValue_ = false;
        }
        return *this;
    }
    
    // Access
    bool hasValue() const { return hasValue_; }
    
    T& value() {
        if (!hasValue_) throw std::runtime_error("Box is empty");
        return *data_;
    }
    
    const T& value() const {
        if (!hasValue_) throw std::runtime_error("Box is empty");
        return *data_;
    }
    
    // Get or default
    T valueOr(const T& defaultValue) const {
        return hasValue_ ? *data_ : defaultValue;
    }
};

int main() {
    Box<int> box1(42);
    Box<int> box2;  // Empty
    
    std::cout << box1.value() << std::endl;       // 42
    std::cout << box2.valueOr(0) << std::endl;    // 0
    
    Box<std::string> strBox("Hello");
    strBox.value() += " World";
    std::cout << strBox.value() << std::endl;     // Hello World
    
    return 0;
}

Multiple Template Parameters

cpp
template<typename K, typename V>
class Pair {
private:
    K key_;
    V value_;
    
public:
    Pair(const K& key, const V& value) : key_(key), value_(value) {}
    
    K& key() { return key_; }
    const K& key() const { return key_; }
    
    V& value() { return value_; }
    const V& value() const { return value_; }
};

int main() {
    Pair<std::string, int> age("Alice", 25);
    Pair<int, double> coord(10, 3.14);
    
    std::cout << age.key() << ": " << age.value() << std::endl;
    // Alice: 25
    
    return 0;
}

Member Functions Outside Class

cpp
template<typename T>
class Stack {
private:
    std::vector<T> data_;
    
public:
    void push(const T& value);
    void pop();
    T& top();
    const T& top() const;
    bool empty() const;
    std::size_t size() const;
};

// Định nghĩa bên ngoài — phải repeat template<typename T>
template<typename T>
void Stack<T>::push(const T& value) {
    data_.push_back(value);
}

template<typename T>
void Stack<T>::pop() {
    if (data_.empty()) {
        throw std::runtime_error("Stack is empty");
    }
    data_.pop_back();
}

template<typename T>
T& Stack<T>::top() {
    if (data_.empty()) {
        throw std::runtime_error("Stack is empty");
    }
    return data_.back();
}

template<typename T>
const T& Stack<T>::top() const {
    if (data_.empty()) {
        throw std::runtime_error("Stack is empty");
    }
    return data_.back();
}

template<typename T>
bool Stack<T>::empty() const {
    return data_.empty();
}

template<typename T>
std::size_t Stack<T>::size() const {
    return data_.size();
}

Non-type Template Parameters

cpp
template<typename T, std::size_t Capacity>
class FixedArray {
private:
    T data_[Capacity];
    std::size_t size_ = 0;
    
public:
    void push_back(const T& value) {
        if (size_ >= Capacity) {
            throw std::overflow_error("Array is full");
        }
        data_[size_++] = value;
    }
    
    T& operator[](std::size_t index) {
        return data_[index];
    }
    
    constexpr std::size_t capacity() const { return Capacity; }
    std::size_t size() const { return size_; }
};

int main() {
    FixedArray<int, 10> arr;  // Fixed capacity of 10
    arr.push_back(1);
    arr.push_back(2);
    
    std::cout << "Capacity: " << arr.capacity() << std::endl;  // 10
    std::cout << "Size: " << arr.size() << std::endl;          // 2
    
    return 0;
}

Default Template Arguments

cpp
template<typename T = int, typename Allocator = std::allocator<T>>
class MyVector {
private:
    T* data_;
    std::size_t size_;
    std::size_t capacity_;
    Allocator alloc_;
    
public:
    // Implementation...
};

int main() {
    MyVector<> v1;              // T=int, Allocator=std::allocator<int>
    MyVector<double> v2;        // T=double, Allocator=std::allocator<double>
    MyVector<int, CustomAlloc> v3;  // Custom allocator
    
    return 0;
}

Nested Classes

cpp
template<typename T>
class LinkedList {
public:
    // Nested Node class
    struct Node {
        T data;
        Node* next;
        
        Node(const T& d) : data(d), next(nullptr) {}
    };
    
private:
    Node* head_ = nullptr;
    
public:
    void pushFront(const T& value) {
        Node* newNode = new Node(value);
        newNode->next = head_;
        head_ = newNode;
    }
    
    ~LinkedList() {
        while (head_) {
            Node* temp = head_;
            head_ = head_->next;
            delete temp;
        }
    }
};

Template Friends

cpp
template<typename T>
class Box;

// Forward declare operator<<
template<typename T>
std::ostream& operator<<(std::ostream& os, const Box<T>& box);

template<typename T>
class Box {
private:
    T value_;
    
public:
    Box(T value) : value_(value) {}
    
    // Friend declaration cho template operator<<
    friend std::ostream& operator<< <T>(std::ostream& os, const Box<T>& box);
};

// Definition
template<typename T>
std::ostream& operator<<(std::ostream& os, const Box<T>& box) {
    return os << "Box(" << box.value_ << ")";
}

int main() {
    Box<int> box(42);
    std::cout << box << std::endl;  // Box(42)
    return 0;
}

Practical Example: SimpleOptional

cpp
template<typename T>
class SimpleOptional {
private:
    alignas(T) unsigned char storage_[sizeof(T)];
    bool hasValue_ = false;
    
    T* ptr() { return reinterpret_cast<T*>(storage_); }
    const T* ptr() const { return reinterpret_cast<const T*>(storage_); }
    
public:
    SimpleOptional() = default;
    
    SimpleOptional(const T& value) : hasValue_(true) {
        new (storage_) T(value);
    }
    
    ~SimpleOptional() {
        if (hasValue_) {
            ptr()->~T();
        }
    }
    
    explicit operator bool() const { return hasValue_; }
    
    T& operator*() { return *ptr(); }
    const T& operator*() const { return *ptr(); }
    
    T* operator->() { return ptr(); }
    const T* operator->() const { return ptr(); }
};

int main() {
    SimpleOptional<std::string> opt("Hello");
    
    if (opt) {
        std::cout << *opt << std::endl;  // Hello
        std::cout << opt->length() << std::endl;  // 5
    }
    
    return 0;
}

📚 Tổng kết

FeatureExample
Basic class templatetemplate<typename T> class Box { T value_; };
Multiple paramstemplate<typename K, typename V> class Pair { };
Non-type paramtemplate<typename T, int N> class Array { T data_[N]; };
Defaulttemplate<typename T = int> class Container { };
Member outsidetemplate<typename T> void Stack<T>::push(...) { }

➡️ Tiếp theo

Tiếp theo: Template Specialization — Handle edge cases.