Modele de metaprogramare șablon
Stăpânește modele de șabloane avansate: CRTP pentru polimorfism static, proiectare bazată pe politici pentru o compoziție flexibilă, ștergere tip pentru generice de rulare și linii directoare pentru când să folosești șabloane față de dispecerare virtuală.
Dincolo de șabloane de bază
Șabloanele permit modele de design care nu au echivalent în alte limbi. Aceste modele exploatează natura în timp de compilare a șabloanelor pentru a obține polimorfism static (fără suprasarcină a funcției virtuale), comportament compus (politici) și ștergere sigură de tip (ascunderea tipurilor concrete în spatele unei interfețe uniforme).
Acestea nu sunt modele pentru începători - sunt instrumente pentru autorii de biblioteci și designerii de cadre. Înțelegerea acestora este esențială pentru citirea elementelor interne standard ale bibliotecii, Boost și cadrelor moderne C++.
CRTP: Model șablon recurent în mod curios
În CRTP, o clasă derivă dintr-un șablon instanțiat cu însuși ca argument:class Derived : public Base. Clasa de bază poate apela metode din clasa derivată la timp de compilare - fără suprasarcină virtuală de expediere. Acesta este folosit pentru a injecta comportament, pentru a implementa polimorfismul static și pentru a crea funcționalitatea mixin.
#include <iostream>
// CRTP base: provides a counter for each derived class
template <typename Derived>
class Counter {
static inline int count_ = 0;
protected:
Counter() { ++count_; }
~Counter() { --count_; }
Counter(const Counter&) { ++count_; }
public:
static int alive() { return count_; }
};
// Each derived class gets its OWN counter
class Dog : public Counter<Dog> {
public:
std::string name;
Dog(std::string n) : name(std::move(n)) {}
};
class Cat : public Counter<Cat> {
public:
std::string name;
Cat(std::string n) : name(std::move(n)) {}
};
// CRTP for static polymorphism: no virtual dispatch
template <typename Derived>
class Shape {
public:
double area() const {
// Call derived class method — resolved at compile time
return static_cast<const Derived*>(this)->area_impl();
}
};
class Circle : public Shape<Circle> {
double radius_;
public:
Circle(double r) : radius_(r) {}
double area_impl() const { return 3.14159265 * radius_ * radius_; }
};
class Square : public Shape<Square> {
double side_;
public:
Square(double s) : side_(s) {}
double area_impl() const { return side_ * side_; }
};
int main() {
Dog d1("Rex"), d2("Buddy");
Cat c1("Whiskers");
std::cout << "Dogs alive: " << Dog::alive() << '\n'; // 2
std::cout << "Cats alive: " << Cat::alive() << '\n'; // 1
Circle circle(5.0);
Square square(4.0);
std::cout << "Circle area: " << circle.area() << '\n'; // 78.54
std::cout << "Square area: " << square.area() << '\n'; // 16.0
}Proiectare bazată pe politici
Design bazat pe politici descompune o clasă în componente de comportament interschimbabile (politici) transmise ca parametri șablon. Fiecare politică implementează un aspect al comportamentului. Utilizatorii compun combinația exactă de care au nevoie. Acest lucru a fost popularizat de „Designul modern C++” al lui Andrei Alexandrescu.
#include <iostream>
#include <mutex>
#include <stdexcept>
// Threading policies
struct SingleThreaded {
struct Lock { Lock(SingleThreaded&) {} }; // no-op
};
struct MultiThreaded {
std::mutex mtx;
struct Lock {
std::lock_guard<std::mutex> guard;
Lock(MultiThreaded& host) : guard(host.mtx) {}
};
};
// Bounds checking policies
struct NoBoundsCheck {
static void check(int /*index*/, int /*size*/) {} // no-op
};
struct StrictBoundsCheck {
static void check(int index, int size) {
if (index < 0 || index >= size)
throw std::out_of_range("Index out of bounds");
}
};
// Policy-based container: users choose their combination
template <typename T,
typename ThreadingPolicy = SingleThreaded,
typename BoundsPolicy = NoBoundsCheck>
class SmartArray : private ThreadingPolicy {
T* data_;
int size_;
public:
SmartArray(int size) : data_(new T[size]{}), size_(size) {}
~SmartArray() { delete[] data_; }
T& operator[](int index) {
typename ThreadingPolicy::Lock lock(*this);
BoundsPolicy::check(index, size_);
return data_[index];
}
int size() const { return size_; }
};
int main() {
// Fast, no overhead: no locking, no bounds checks
SmartArray<int> fast(10);
fast[0] = 42;
// Safe: bounds-checked, single-threaded
SmartArray<int, SingleThreaded, StrictBoundsCheck> safe(10);
safe[0] = 42;
try {
safe[99] = 0; // throws!
} catch (const std::out_of_range& e) {
std::cout << e.what() << '\n';
}
// Thread-safe with bounds checking
SmartArray<int, MultiThreaded, StrictBoundsCheck> robust(10);
robust[5] = 100;
}Tip ștergere
Ștergerea tipului ascunde tipul concret în spatele unei interfețe uniforme, combinând flexibilitatea șabloanelor cu polimorfismul de rulare al funcțiilor virtuale. Exemplul clasic estestd::function, care poate conține orice apelabil. Modelul folosește trei componente: un concept (interfață abstractă), un model (înveliș de șablon care implementează interfața pentru orice tip) și o clasă mâner exterior.
#include <iostream>
#include <memory>
#include <string>
// Type-erased "Printable" — can hold any type with a print() method
class Printable {
// Concept: abstract interface
struct Concept {
virtual ~Concept() = default;
virtual void print(std::ostream& os) const = 0;
virtual std::unique_ptr<Concept> clone() const = 0;
};
// Model: wraps any T that supports operator<<
template <typename T>
struct Model : Concept {
T value;
Model(T v) : value(std::move(v)) {}
void print(std::ostream& os) const override { os << value; }
std::unique_ptr<Concept> clone() const override {
return std::make_unique<Model>(value);
}
};
std::unique_ptr<Concept> ptr_;
public:
// Constructor: accepts any printable type
template <typename T>
Printable(T value) : ptr_(std::make_unique<Model<T>>(std::move(value))) {}
// Copy support
Printable(const Printable& other) : ptr_(other.ptr_->clone()) {}
Printable& operator=(const Printable& other) {
ptr_ = other.ptr_->clone();
return *this;
}
Printable(Printable&&) = default;
Printable& operator=(Printable&&) = default;
friend std::ostream& operator<<(std::ostream& os, const Printable& p) {
p.ptr_->print(os);
return os;
}
};
int main() {
// Heterogeneous collection — no common base class needed!
std::vector<Printable> items;
items.emplace_back(42);
items.emplace_back(3.14);
items.emplace_back(std::string("hello"));
items.emplace_back('X');
for (const auto& item : items) {
std::cout << item << '\n';
}
// Output: 42, 3.14, hello, X
}Șabloane de expresie (prezentare generală)
Șabloanele de expresie sunt o tehnică în care expresiile aritmetice pe obiecte construiesc un arboresc în timp de compilare de operații, mai degrabă decât să calculeze rezultate intermediare. Rezultatul final este calculat într-o singură trecere, eliminând obiectele temporare.
De exemplu, cu o clasăVectornaivă,a + b + ccreează doi vectori temporari. Cu șabloane de expresie,a + b + cconstruiește un obiect de expresie ușorAddcare calculează elemente la cerere - nu sunt necesare temporare.
Această tehnică este utilizată de bibliotecile de algebră liniară de înaltă performanță precum Eigen și Blaze. Arborele de expresie este evaluat leneș, iar compilatorul poate vectoriza adesea bucla cu o singură trecere rezultată.
Șabloanele de expresie sunt complexe de implementat corect și sunt în primul rând o preocupare a autorului bibliotecii. Dacă aveți nevoie de matematică matrice/vectorală, utilizați o bibliotecă precum Eigen în loc să implementați singur șabloane de expresie.
Când alegeți între șabloane (polimorfism static) și funcții virtuale (polimorfism dinamic):
Folosiți șabloane când:
- Setul de tipuri este cunoscut la momentul compilarii
- Aveți nevoie de abstracții zero-overhead (fără vtable, fără indirecte)
- Doriți ca compilatorul să fie integrat și să optimizeze peste granițele de tip
- Trebuie să lucrați cu semantica valorii (fără alocare heap)
- Performanța este critică (bucle strânse, cod numeric)
Utilizați funcțiile virtuale când:
- Setul de tipuri este deschis și determinat în timpul execuției (plugin-uri, intrare de utilizator)
- Aveți nevoie de containere eterogene (de exemplu,vector)
- Dimensiunea binară contează mai mult decât viteza (șabloanele generează mai mult cod)
- Doriți ABI stabil peste limitele bibliotecii partajate
- Trebuie să adăugați noi tipuri fără a recompila codul existent
Utilizați ștergerea tipului atunci când:
- Doriți simplitatea interfeței dispecerului virtual, dar nu doriți să forțați utilizatorii să moștenească din clasa dvs. de bază
- Aveți nevoie de semantică valorică cu polimorfism de rulare (std::function,std::any)
Template metaprogramming is powerful but has real costs:
1. Compile-time explosion — deeply nested template instantiations can make compile times unacceptable. Recursive TMP is especially bad — prefer fold expressions andif constexpr.
2. Error messages — without concepts, template errors produce pages of incomprehensible text. Always constrain templates.
3. Debugging — stepping through template-heavy code in a debugger is painful. Type-erased wrappers add indirection that hides the actual type.
4. Readability — CRTP, policy-based design, and expression templates are hard for junior developers to understand. Document the pattern name and intent.
5. Overengineering — not every problem needs a template solution. A simple virtual function or even a switch statement may be clearer and sufficient.
- CRTP permite polimorfismul static și contoarele/mixinele pe clasă, derivând din
Base - Policy-based design decomposes behavior into template parameters — users compose the exact combination they need
- Type erasure combines template flexibility with runtime polymorphism (e.g.,
std::function,std::any) - Expression templates eliminate temporaries in chained operations — used by Eigen, Blaze, and similar libraries
- Alegeți șabloane pentru tipurile cunoscute și zero overhead; dispecerare virtuală pentru seturi de tip deschis și flexibilitate de rulare
- Avoid overengineering — use the simplest mechanism that meets your requirements
Test — Verifică-ți cunoștințele
(20 XP)1. In CRTP, what does`class Dog : public Counter<Dog>`achieve?
2. What problem does type erasure solve?
3. Când ar trebui să preferați funcțiile virtuale decât șabloane?