C++ 模板一些机制

2025-10-12

C++ 模板一些机制

1. 动态多态(Dynamic Polymorphism)

概念: 动态多态是运行时多态,通过虚函数(virtual functions)继承实现。程序在运行时根据对象的实际类型调用对应的函数。

实现机制:

  • 基类中声明虚函数(virtual)。
  • 派生类重写(override)这些虚函数。
  • 通过基类指针或引用调用函数时,运行时根据实际对象类型决定调用哪个版本。

例如:

#include <iostream>
struct Base {
    virtual void foo() { std::cout << "Base::foo\n"; }
    virtual ~Base() = default;
};

struct Derived : Base {
    void foo() override { std::cout << "Derived::foo\n"; }
};

int main() {
    Base* b = new Derived();
    b->foo(); // 输出: Derived::foo(运行时决定)
    delete b;
}

优点:

  • 灵活,支持运行时类型变化。
  • 易于扩展(开闭原则)。

缺点:

  • 有运行时开销(虚函数表查找)。
  • 无法内联优化。
  • 需要堆分配或指针/引用,不能直接使用值语义。

2. 静态多态(Static Polymorphism)

概念:

静态多态是编译时多态,通过模板(templates) 实现。函数或类的行为在编译期根据类型参数确定,无需运行时开销。

实现方式:

  • 函数模板重载
  • 类模板特化
  • CRTP(见下文)

例如:

template<typename T>
void print(T x) {
    std::cout << x << '\n';
}

print(42);        // 实例化为 print<int>
print("hello");   // 实例化为 print<const char*>

与动态多态对比:

  • 无虚函数开销
  • 可内联
  • 类型安全更强
  • 代码膨胀(每个类型实例化一份代码)

3. CRTP(Curiously Recurring Template Pattern)

概念: CRTP 是一种实现静态多态的设计模式,通过让派生类作为模板参数传递给基类,使基类能在编译期“知道”派生类类型,从而调用派生类的方法。

template<typename Derived>
struct Base {
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

struct MyDerived : Base<MyDerived> {
    void implementation() { std::cout << "MyDerived impl\n"; }
};

用途:

  • 实现静态多态(替代虚函数)
  • 提供通用接口(如 operator<, clone, serialize 等)
  • 避免虚函数开销

例如:实现比较操作符

template<typename Derived>
struct Comparable {
    bool operator<(const Derived& other) const {
        return static_cast<const Derived*>(this)->compare(other) < 0;
    }
    bool operator==(const Derived& other) const {
        return static_cast<const Derived*>(this)->compare(other) == 0;
    }
};

struct Point : Comparable<Point> {
    int x, y;
    int compare(const Point& other) const {
        if (x != other.x) return x - other.x;
        return y - other.y;
    }
};

优点:

  • 零运行时开销
  • 支持值语义
  • 可内联

缺点:

  • 代码可读性稍差
  • 调试困难(模板错误信息冗长)
  • 无法在运行时切换行为

4. CTAD(Class Template Argument Deduction)

概念: C++17 引入的特性,允许编译器根据构造函数的实参自动推导类模板的模板参数,无需显式指定类型。

在c++17 之前,必须写:

std::pair<int, double> p(1, 2.5);

现在可以写:

std::pair p(1, 2.5); // 自动推导为 pair<int, double>

原理:

  • 编译器为类模板生成“推导指引(deduction guides)”
  • 可以显式定义推导指引

例如:

template<typename T>
struct Container {
    T value;
    Container(T v) : value(v) {}
};

// C++17 起:
Container c(42);        // T 推导为 int
Container d(3.14);      // T 推导为 double
template<typename T>
struct MyVector {
    MyVector(std::initializer_list<T> list);
};

// 显式推导指引(如果需要)
template<typename T>
MyVector(std::initializer_list<T>) -> MyVector<T>;

MyVector v{1, 2, 3}; // 推导为 MyVector<int>

优点:

  • 减少样板代码
  • 提高可读性和易用性
  • auto 风格一致

5. SFINAE(Substitution Failure Is Not An Error)

概念: SFINAE 是 C++ 模板元编程中的核心原则:在模板参数替换过程中,如果替换导致类型错误,该模板不会被选中,而不是引发编译错误

用途:

  • 条件性地启用/禁用函数模板或类模板特化
  • 实现类型特征(type traits)和约束

C++20 之前使用std::enable_if, C++20 支持使用requires

#include <type_traits>

template<typename T>
typename std::enable_if<std::is_integral_v<T>, void>::type  // 如果是int 类型数据
foo(T x) {
    std::cout << "Integral: " << x << '\n';
}

template<typename T>
typename std::enable_if<!std::is_integral_v<T>, void>::type // 如果不是int 类型数据
foo(T x) {
    std::cout << "Non-integral: " << x << '\n';
}

foo(42);      // 调用第一个
foo(3.14);    // 调用第二个

C++ 20

template<typename T>
requires std::is_integral_v<T>
void foo(T x) { /* ... */ }

SFINAE 的工作原理:

  1. 编译器尝试实例化所有匹配的模板。
  2. 对每个模板,尝试替换模板参数。
  3. 如果替换导致无效类型(如 typename T::typeT 没有 type),则丢弃该候选,不报错。
  4. 如果最终有且仅有一个有效候选,则使用它;否则重载解析失败。

6. Concepts

C++20 引入了concepts, 可以用来代替SFINAE, 提供更加清晰的语法.

例如:自定义conecpts

#include <concepts>
#include <iostream>

template<typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;  // 要求 a + b 的结果类型仍是 T
};

template<Addable T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add(2, 3) << '\n';        // OK → 5
    std::cout << add(1.5, 2.5) << '\n';    // OK → 4.0
}

检查是否像STL容器

#include <concepts>
#include <vector>
#include <list>
#include <iostream>

template<typename T>
concept Container = requires(T c) {
    typename T::value_type;
    { c.size() } -> std::convertible_to<std::size_t>;
    { c.empty() } -> std::convertible_to<bool>;
    c.begin();
    c.end();
    requires std::input_iterator<decltype(c.begin())>;
};

template<Container C>
void print_size(const C& container) {
    std::cout << "Size: " << container.size() << '\n';
}

int main() {
    std::vector<int> v = {1, 2, 3};
    std::list<std::string> l = {"a", "b"};

    print_size(v); // OK
    print_size(l); // OK

    // print_size(42); // ❌ int 不是容器
}

要求一个类型可以向函数一样被调用:

#include <concepts>
#include <iostream>

template<typename F, typename T>
concept Predicate = std::invocable<F, T> &&
                   std::convertible_to<std::invoke_result_t<F, T>, bool>;

template<Predicate<int> Func>
void test_int(Func f) {
    if (f(42)) {
        std::cout << "Predicate true for 42\n";
    }
}

int main() {
    auto is_even = [](int x) { return x % 2 == 0; };
    auto is_positive = [](int x) -> bool { return x > 0; };

    test_int(is_even);      // OK
    test_int(is_positive);  // OK

    // test_int([](double x) { return x > 0; }); // ❌ 不能用 int 调用
}

重载基于concepts

#include <concepts>
#include <iostream>

void handle(auto x) {
    std::cout << "Generic: " << x << '\n';
}

void handle(std::integral auto x) {
    std::cout << "Integral: " << x << '\n';
}

void handle(std::floating_point auto x) {
    std::cout << "Floating: " << x << '\n';
}

int main() {
    handle(10);    // → Integral
    handle(3.14);  // → Floating
    handle("hi");  // → Generic
}