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 的工作原理:
- 编译器尝试实例化所有匹配的模板。
- 对每个模板,尝试替换模板参数。
- 如果替换导致无效类型(如
typename T::type
但T
没有type
),则丢弃该候选,不报错。 - 如果最终有且仅有一个有效候选,则使用它;否则重载解析失败。
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
}