C++ 泛型编程

C++ 泛型编程 是C++中最强大的特性之一,它允许我们编写与类型无关的代码。本文将深入探讨C++模板的各个方面,包括其编译机制、特化、概念等高级特性。

1. 模板基础

模板是C++实现泛型编程的核心机制。它允许我们编写可以处理不同数据类型的代码,而不需要为每种类型重写代码。

1.1 函数模板

函数模板是最基本的模板形式。让我们看一个简单的例子:

cpp 复制代码
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 使用示例
int main() {
    int i = max(10, 20);        // T 被推导为 int
    double d = max(3.14, 2.72); // T 被推导为 double
    return 0;
}

1.2 类模板

类模板允许我们定义可以处理不同数据类型的类:

cpp 复制代码
template<typename T>
class Stack {
private:
    std::vector<T> elements;
public:
    void push(const T& element) {
        elements.push_back(element);
    }
    
    T pop() {
        T element = elements.back();
        elements.pop_back();
        return element;
    }
    
    bool empty() const {
        return elements.empty();
    }
};

// 使用示例
int main() {
    Stack<int> intStack;    // 整数栈
    Stack<string> strStack; // 字符串栈
    return 0;
}

1.3 变量模板

C++14引入了变量模板,允许我们定义模板变量:

cpp 复制代码
template<typename T>
constexpr T pi = T(3.1415926535897932385);

int main() {
    float f = pi<float>;
    double d = pi<double>;
    return 0;
}

1.4 Lambda函数与模板

C++14引入了泛型Lambda(Generic Lambda),允许在Lambda函数中使用模板。这使得Lambda函数更加灵活和强大。

1.5.1 多参数模板Lambda

cpp 复制代码
// 多个auto参数
auto add = [](auto a, auto b) {
    return a + b;
};

// 使用示例
auto result1 = add(1, 2);        // 整数加法
auto result2 = add(1.5, 2.5);    // 浮点数加法
auto result3 = add("Hello", " World");  // 字符串连接

1.5.2 C++20中的显式模板Lambda

C++20允许在Lambda函数中使用显式模板语法,这提供了更好的类型控制和文档性:

cpp 复制代码
// C++20之前的写法(使用auto)
auto oldStyle = [](auto x) { return x * 2; };

// C++20中的显式模板语法
auto newStyle = []<typename T>(T x) { return x * 2; };

// 多参数模板
auto multiParam = []<typename T, typename U>(T a, U b) {
    return a + b;
};

// 使用示例
int main() {
    // 使用显式模板Lambda
    auto result1 = newStyle(42);           // T 被推导为 int
    auto result2 = newStyle(3.14);         // T 被推导为 double
    
    // 使用多参数模板Lambda
    auto sum1 = multiParam(1, 2.5);        // T=int, U=double
    auto sum2 = multiParam("Hello", 42);   // T=const char*, U=int
    
    return 0;
}

2. 模板编译机制

2.1 编译时机

C++模板采用"两阶段编译"机制:

  1. 模板定义阶段:检查模板语法
  2. 模板实例化阶段:生成具体类型的代码
cpp 复制代码
template<typename T>
void process(T value) {
    value.non_existent_method(); // 在定义阶段不会报错
}

int main() {
    process(42); // 在实例化阶段才会报错
    return 0;
}

2.2 选择性实例化

编译器对模板方法的处理方式:

  • 虚方法:总是生成代码
  • 非虚方法:只为实际调用的方法生成代码

示例:

cpp 复制代码
template<typename T>
class Container {
public:
    virtual void process() { /* 总是生成代码 */ }
    void sort() { /* 只在调用时生成代码 */ }
    void filter() { /* 只在调用时生成代码 */ }
};

Container<int> c;
c.sort();  // 只生成sort方法的代码

2.3 显式实例化

我们可以显式地告诉编译器实例化特定的模板:

cpp 复制代码
template<typename T>
class MyClass {
    // ... 类定义
};

// 显式实例化声明
extern template class MyClass<int>;

// 显式实例化定义
template class MyClass<int>;

3. 类模板实参推导 (CTAD)(Class Template Argument Deduction)

2.1 基本用法

cpp 复制代码
// 传统方式
std::pair<int, double> p1(1, 2.0);

// 使用CTAD
std::pair p2(1, 2.0);  // 自动推导为pair<int, double>

2.2 智能指针注意事项

cpp 复制代码
// 错误:类型推导被禁用
std::unique_ptr ptr(new int(42));  // 编译错误

// 正确:使用make函数
auto ptr = std::make_unique<int>(42);
auto sptr = std::make_shared<std::string>("Hello");

2.3 自定义推导指引

cpp 复制代码
template<typename T>
class StringWrapper {
public:
    StringWrapper(T str) : data(str) {}
private:
    T data;
};

// 自定义推导指引
StringWrapper(const char*) -> StringWrapper<std::string>;
StringWrapper(const wchar_t*) -> StringWrapper<std::wstring>;

// 使用示例
StringWrapper s1("Hello");  // 推导为StringWrapper<std::string>
StringWrapper s2(L"World"); // 推导为StringWrapper<std::wstring>

4. 模板特化

模板特化允许我们为特定的类型或类型组合提供专门的实现。特化分为两种:全特化和偏特化。

4.1 全特化

全特化是指为模板参数指定具体的类型,完全替换原始模板的实现。

4.1.1 类模板全特化

cpp 复制代码
// 原始模板
template<typename T>
class Container {
public:
    void process(T value) {
        std::cout << "处理通用类型: " << value << std::endl;
    }
};

// 全特化:为bool类型提供专门实现
template<>
class Container<bool> {
public:
    void process(bool value) {
        std::cout << "处理布尔值: " << (value ? "true" : "false") << std::endl;
    }
};

// 使用示例
int main() {
    Container<int> c1;
    c1.process(42);    // 输出:处理通用类型: 42

    Container<bool> c2;
    c2.process(true);  // 输出:处理布尔值: true
    return 0;
}

4.1.2 函数模板全特化

cpp 复制代码
// 原始模板
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 全特化:为const char*类型提供专门实现
template<>
const char* max(const char* a, const char* b) {
    return (strcmp(a, b) > 0) ? a : b;
}

// 使用示例
int main() {
    int i = max(10, 20);                    // 使用原始模板
    const char* s = max("hello", "world");  // 使用特化版本
    return 0;
}

4.2 偏特化

偏特化是指为模板参数指定部分具体的类型,或者指定类型之间的关系。注意:函数模板不支持偏特化,只有类模板支持。

4.2.1 指针类型的偏特化

cpp 复制代码
// 原始模板
template<typename T>
class Container {
public:
    void process(T value) {
        std::cout << "处理值: " << value << std::endl;
    }
};

// 偏特化:为所有指针类型提供专门实现
template<typename T>
class Container<T*> {
public:
    void process(T* value) {
        std::cout << "处理指针: " << *value << std::endl;
    }
};

// 使用示例
int main() {
    Container<int> c1;
    int x = 42;
    c1.process(x);     // 输出:处理值: 42

    Container<int*> c2;
    c2.process(&x);    // 输出:处理指针: 42
    return 0;
}

4.2.2 多参数模板的偏特化

cpp 复制代码
// 原始模板
template<typename T, typename U>
class Pair {
public:
    void print() {
        std::cout << "通用Pair" << std::endl;
    }
};

// 偏特化:当两个类型相同时
template<typename T>
class Pair<T, T> {
public:
    void print() {
        std::cout << "相同类型Pair" << std::endl;
    }
};

// 偏特化:当第二个类型是第一个类型的指针时
template<typename T>
class Pair<T, T*> {
public:
    void print() {
        std::cout << "指针类型Pair" << std::endl;
    }
};

// 使用示例
int main() {
    Pair<int, double> p1;    // 使用原始模板
    p1.print();              // 输出:通用Pair

    Pair<int, int> p2;       // 使用第一个偏特化
    p2.print();              // 输出:相同类型Pair

    Pair<int, int*> p3;      // 使用第二个偏特化
    p3.print();              // 输出:指针类型Pair
    return 0;
}

4.3 特化的注意事项

  1. 函数模板只支持全特化,不支持偏特化。如果需要类似偏特化的功能,可以使用函数重载或SFINAE。

  2. 特化的声明顺序很重要:

  • 必须先声明原始模板
  • 然后才能声明特化版本
  • 特化版本必须与原始模板的接口完全匹配
  1. 特化的常见用途:
  • 为特定类型提供更高效的实现
  • 处理特殊类型(如指针、引用)
  • 提供类型特定的行为
  • 优化特定类型组合的性能

5. 可变参数模板

可变参数模板(Variadic Templates)是 C++11 引入的一个重要特性,它允许模板接受任意数量的模板参数。这个特性在实现通用函数和类时非常有用。

5.1 基本语法

可变参数模板使用 ... 语法来表示可变数量的参数:

cpp 复制代码
// 函数模板
template<typename... Args>
void print(Args... args);

// 类模板
template<typename... Types>
class Tuple;

5.2 参数包展开

参数包可以通过多种方式展开:

  1. 递归展开:
cpp 复制代码
// 基本情况
void print() {
    std::cout << std::endl;
}

// 递归情况
template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);  // 递归调用,展开剩余参数
}

// 使用示例
int main() {
    print(1, 2.5, "hello", 'a');  // 输出:1 2.5 hello a
    return 0;
}
  1. 折叠表达式(C++17):
cpp 复制代码
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 右折叠
    // 或者使用左折叠
    // return (... + args);
}

// 使用示例
int main() {
    auto result = sum(1, 2, 3, 4, 5);  // 计算 1 + 2 + 3 + 4 + 5
    return 0;
}

5.3 实际应用示例

  1. 完美转发:
cpp 复制代码
template<typename... Args>
class Logger {
public:
    template<typename... ConstructorArgs>
    Logger(ConstructorArgs&&... args) {
        // 完美转发所有参数
        log("Constructor called with", std::forward<ConstructorArgs>(args)...);
    }

private:
    template<typename... LogArgs>
    void log(LogArgs&&... args) {
        (std::cout << ... << args) << std::endl;
    }
};
  1. 元组实现:
cpp 复制代码
template<typename... Types>
class Tuple;

// 基本情况:空元组
template<>
class Tuple<> {};

// 递归情况:非空元组
template<typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
public:
    Tuple(const Head& head, const Tail&... tail)
        : Tuple<Tail...>(tail...), head_(head) {}

    Head& getHead() { return head_; }
    Tuple<Tail...>& getTail() { return *this; }

private:
    Head head_;
};

// 使用示例
int main() {
    Tuple<int, double, std::string> t(1, 2.5, "hello");
    std::cout << t.getHead() << std::endl;
    return 0;
}
  1. 函数包装器:
cpp 复制代码
template<typename Func, typename... Args>
auto callWithLogging(Func&& func, Args&&... args) {
    std::cout << "Calling function with " << sizeof...(args) << " arguments" << std::endl;
    return std::forward<Func>(func)(std::forward<Args>(args)...);
}

// 使用示例
int add(int a, int b) { return a + b; }

int main() {
    auto result = callWithLogging(add, 1, 2);
    return 0;
}

5.4 参数包操作

  1. 获取参数包大小:
cpp 复制代码
template<typename... Args>
void printSize(Args... args) {
    std::cout << "Number of arguments: " << sizeof...(args) << std::endl;
}
  1. 条件展开:
cpp 复制代码
template<typename... Args>
void printIf(Args... args) {
    (void)((std::cout << args << " ", 0) + ...);  // 使用逗号运算符
    std::cout << std::endl;
}

5.5 注意事项

  1. 递归展开可能导致编译时间增加,应谨慎使用。

  2. 参数包展开时要注意参数顺序:

cpp 复制代码
template<typename... Args>
void process(Args... args) {
    // 注意参数展开顺序
    (processSingle(args), ...);  // 从左到右
    // 或者
    (..., processSingle(args));  // 从右到左
}
  1. 在类模板中使用时,需要注意继承和成员变量的布局:
cpp 复制代码
template<typename... Bases>
class Derived : public Bases... {
    // 多重继承,每个基类都是参数包中的一个类型
};
  1. 在函数模板中使用时,要注意参数的生命周期:
cpp 复制代码
template<typename... Args>
void store(Args&&... args) {
    // 使用完美转发保存参数
    (storage.emplace_back(std::forward<Args>(args)), ...);
}

5.6 高级应用

  1. 类型列表操作:
cpp 复制代码
template<typename... Types>
struct TypeList {};

// 获取第一个类型
template<typename T, typename... Rest>
struct First {
    using type = T;
};

// 获取最后一个类型
template<typename T>
struct Last {
    using type = T;
};

template<typename T, typename... Rest>
struct Last<T, Rest...> {
    using type = typename Last<Rest...>::type;
};
  1. 条件编译:
cpp 复制代码
template<typename... Args>
struct AllIntegral {
    static constexpr bool value = (std::is_integral_v<Args> && ...);
};

template<typename... Args>
void processIfIntegral(Args... args) {
    if constexpr (AllIntegral<Args...>::value) {
        // 处理整数参数
    }
}

6. Type Traits

Type Traits 提供了在编译时检查和操作类型的能力:

  • 一个模板化的结构,通常以的类型特征命名。例如 is_integer is_pointer is_void 等等
  • 结构包含一个静态 const bool 命名值
  • 对特征的结构进行特化,并且把它们的布尔值设置为一个合理的状态值
  • 查询其值来使用类型特征,如:my_type_trait :: value
cpp 复制代码
template <typename T>
struct is_swapable {
  static const bool value = false;
};


template <>
struct is_swapable<unsigned short> {
  static const bool value = true;
};


template <>
struct is_swapable<short> {
  static const bool value = true;
};
cpp 复制代码
template<typename T>
void process(T value) {
    if constexpr (std::is_integral_v<T>) {
        // 处理整数类型
    } else if constexpr (std::is_floating_point_v<T>) {
        // 处理浮点类型
    } else {
        // 处理其他类型
    }
}

一般类型特征(Primary Type Categories)

std::is_void std::is_integral std::is_floating_point std::is_array std::is_pointer std::is_lvalue_reference std::is_rvalue_reference std::is_enum std::is_union std::is_class std::is_function

复合类型特征(Composite Type Categories)

std::is_arithmetic std::is_fundamental std::is_object std::is_scalar std::is_compound std::is_reference std::is_member_pointer

C++17 引入了变量模板(variable template),为类型特征提供了更简洁的使用方式:

  1. 传统方式(C++11):
cpp 复制代码
template<typename T>
void process(T value) {
    if constexpr (std::is_integral<T>::value) {
        // 处理整数类型
    }
}
  1. 变量模板方式(C++17):
cpp 复制代码
template<typename T>
void process(T value) {
    if constexpr (std::is_integral_v<T>) {
        // 处理整数类型
    }
}

这两种方式是等价的,std::is_integral_v<T> 实际上就是 std::is_integral<T>::value 的简写。变量模板的引入使得代码更加简洁和易读。

7. Concepts (C++20)

Concepts 是 C++20 引入的一个重要特性,它提供了一种在编译时约束模板参数的方式,使得模板代码更加清晰、可读和易于维护。

7.1 Concepts 基础

Concepts 本质上是一个编译时谓词,用于检查类型是否满足特定的要求。它可以:

  • 约束模板参数
  • 提供更好的错误信息
  • 简化模板代码
  • 提高代码的可读性和可维护性

基本语法:

cpp 复制代码
// 定义一个概念
template<typename T>
concept Number = std::is_arithmetic_v<T>;

template <typename T>
concept C = sizeof(T) == 4;

// 使用概念约束模板
template<Number T>
T add(T a, T b) {
    return a + b;
}

// 使用示例
int main() {
    add(1, 2);     // 正确:int 满足 Number 概念
    add(1.5, 2.5); // 正确:double 满足 Number 概念
    add("a", "b"); // 错误:const char* 不满足 Number 概念
    return 0;
}

7.2 组合概念

Concepts 可以通过逻辑运算符组合:

cpp 复制代码
// 定义多个概念
template<typename T>
concept Integral = std::is_integral_v<T>;

template<typename T>
concept Signed = std::is_signed_v<T>;

// 组合概念
template<typename T>
concept SignedIntegral = Integral<T> && Signed<T>;

// 使用组合概念
template<SignedIntegral T>
T absolute(T value) {
    return value < 0 ? -value : value;
}

7.3 requires

Concepts 可以使用要求表达式(requires-expression)来定义更复杂的约束:

  1. 简单要求(Simple Requirement): 验证表达式是否能通过编译
cpp 复制代码
// 简单要求是一个表达式声明,要求该表达式是合法的。它主要用于检查某个操作或函数调用的有效性。
template<typename T>
concept HasIncrement = requires(T a) {
    ++a; // 简单要求:a 必须支持前置自增操作
};
  1. 类型要求(Type Requirement): 验证特定类型是否有效
cpp 复制代码
// 类型要求使用 typename 关键字指定一个类型,要求该类型是合法的。它主要用于检查某个类型表达式是否代表一个有效的类型。
template<typename T>
concept HasIterator = requires {
    typename T::iterator; // 类型要求:T 必须有一个名为 iterator 的嵌套类型
};
  1. 复合要求(Compound Requirement): 验证某些东西不会抛出异常或验证某个方法是否返回某个类型
cpp 复制代码
// 复合要求用于检查表达式的类型、值分类(例如,lvalue 或 rvalue),以及(可选地)期望的返回类型。复合要求以花括号包围一个表达式,并可使用 noexcept 指定表达式必须是无异常的,也可以用 -> 指定表达式的返回类型。

#include <concepts>
template<typename T>
concept Swappable = requires(T a, T b) {
    { std::swap(a, b) } noexcept; // 复合要求:调用 std::swap 必须合法且无异常
    { a < b } -> convertible_to<bool>;
};
  1. 嵌套要求(Nested Requirement)
cpp 复制代码
// 嵌套要求允许在 requires 表达式中使用另一个 requires 表达式。这种要求形式可以包含任何有效的 requires 表达式,它使得可以构建更加复杂和结构化的约束条件。

template<typename T>
concept Incrementable = requires(T a) {
    requires sizeof(T) == 4;
    ++a; // 嵌套要求:a 必须支持前置自增操作
};

7.4 概念约束的多种形式

  1. 在模板参数列表中:
cpp 复制代码
template<typename T>
requires Number<T>
T multiply(T a, T b) {
    return a * b;
}
  1. 在函数声明中:
cpp 复制代码
template<typename T>
T divide(T a, T b) requires Number<T> {
    return a / b;
}
  1. 使用简写语法:
cpp 复制代码
template<Number T>
T subtract(T a, T b) {
    return a - b;
}

7.5 实际应用示例

cpp 复制代码
// 定义一个可比较的概念
template<typename T>
concept Comparable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;
    { a > b } -> std::convertible_to<bool>;
    { a == b } -> std::convertible_to<bool>;
};

// 定义一个可排序的容器概念
template<typename T>
concept SortableContainer = requires(T container) {
    requires Comparable<typename T::value_type>;
    { container.begin() } -> std::same_as<typename T::iterator>;
    { container.end() } -> std::same_as<typename T::iterator>;
    { std::sort(container.begin(), container.end()) };
};

// 使用这些概念实现排序函数
template<SortableContainer Container>
void sortContainer(Container& container) {
    std::sort(container.begin(), container.end());
}

// 定义一个可迭代的概念
template<typename T>
concept Iterable = requires(T t) {
    t.begin();  // 必须有 begin() 方法
    t.end();    // 必须有 end() 方法
    { *t.begin() } -> std::same_as<typename T::value_type>;  // 迭代器解引用类型必须匹配
};

// 定义一个可加的概念
template<typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;  // 必须支持加法操作
    { a += b } -> std::same_as<T&>;  // 必须支持 += 操作
};

// 使用这些概念
template<Iterable T>
void printContainer(const T& container) {
    for (const auto& item : container) {
        std::cout << item << " ";
    }
}

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

// 使用示例
int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9};
    sortContainer(numbers);  // 正确:vector<int> 满足 SortableContainer 概念
    
    std::list<std::string> words = {"hello", "world"};
    sortContainer(words);    // 正确:list<string> 满足 SortableContainer 概念
    
    return 0;
}

8. SFINAE (Substitution Failure Is Not An Error)

SFINAE(Substitution Failure Is Not An Error)是C++模板编程中一个重要的特性,用于在模板实例化时进行条件编译。当一个模板的某个候选项在替换过程中失败(即不满足某些条件),这个失败并不会导致编译错误,而是简单地从候选项列表中移除。SFINAE常常与特化和重载结合使用,以达到根据不同条件选择不同实现的效果。接下来,我们看几个SFINAE的例子。

cpp 复制代码
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
process(T value) {
    return value * 2;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
process(T value) {
    return value * 1.5;
}
  1. 通过返回类型判断成员函数是否存在:

利用SFINAE来检测一个类型是否包含某个特定的成员函数

cpp 复制代码
#include <type_traits>
#include <iostream>

// 声明一个类型检测的结构体,等价于false
template<typename, typename T>
struct has_serialize : std::false_type {};

// 萃取特化,利用SFINAE检测serialize方法
template<typename T>
struct has_serialize<T, decltype((void) std::declval<T>().serialize(), void())> : std::true_type {};

struct Serializer {
    void serialize() const {}
};

struct NonSerializer {};

template<typename T>
void serializeIfPossible(const T& obj) {
    if constexpr (has_serialize<T, void>::value) {
        obj.serialize();
        std::cout << "Serialized" << std::endl;
    } else {
        std::cout << "Serialization not available" << std::endl;
    }
}

int main() {
    Serializer s;
    NonSerializer ns;
    serializeIfPossible(s); // 输出:Serialized
    serializeIfPossible(ns); // 输出:Serialization not available
}
  1. 通过std::enable_if启用或禁用模板

std::enable_if是SFINAE的一个典型应用,可以用来根据条件启用或禁用函数模板。

cpp 复制代码
#include <type_traits>
#include <iostream>

// 对于整数类型的重载
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
max(T x, T y) {
    return (x > y) ? x : y;
}

// 对于浮点类型的重载
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
max(T x, T y) {
    std::cout << "Floating point version called." << std::endl;
    return (x > y) ? x : y;
}

int main() {
    std::cout << max(3, 7) << std::endl; // 调用整数重载
    std::cout << max(3.14, 1.59) << std::endl; // 调用浮点数重载,并打印额外信息
}
  1. 使用尾置返回类型和decltype进行SFINAE

尾置返回类型允许我们在返回类型中使用函数参数,与decltype结合可以用于SFINAE,对不同类型执行不同操作。

cpp 复制代码
#include <iostream>
#include <vector>

// 对于容器类型,返回其大小
template<typename T>
auto getSize(const T& c) -> decltype(c.size(), size_t()) {
    std::cout << "Container version called." << std::endl;
    return c.size();
}

// 对于整数类型,直接返回该值
template<typename T>
auto getSize(const T& i) -> decltype(T(1+1), size_t()) {
    std::cout << "Integer version called." << std::endl;
    return i;
}

int main() {
    std::vector<int> v = {1,2,3};
    std::cout << getSize(v) << std::endl; // 调用容器重载
    std::cout << getSize(42) << std::endl; // 调用整数重载
}

9. 最佳实践

  1. 使用类型别名简化模板
cpp 复制代码
template<typename T>
using Vector = std::vector<T>;

template<typename T>
using UniquePtr = std::unique_ptr<T>;
  1. 使用auto简化模板代码
cpp 复制代码
template<typename Container>
auto getFirst(const Container& c) -> decltype(c.front()) {
    return c.front();
}
  1. 使用static_assert进行编译时检查
cpp 复制代码
template<typename T>
class Stack {
    static_assert(std::is_copy_constructible_v<T>,
                 "T must be copy constructible");
    // ...
};

10. 常见陷阱和解决方案

  1. 模板代码膨胀

    • 使用显式实例化控制代码生成
    • 将通用代码抽取到非模板基类
  2. 编译时间优化

    • 使用前向声明
    • 将模板实现分离到.cpp文件
    • 使用预编译头文件
  3. 调试技巧

    • 使用static_assert进行类型检查
    • 使用typeid打印类型信息
    • 使用编译器特定的调试选项

总结

C++模板是一个强大而复杂的特性,它为我们提供了:

  • 类型安全的泛型编程
  • 编译时多态
  • 零开销抽象
  • 强大的元编程能力

通过合理使用模板,我们可以编写出既灵活又高效的代码。然而,模板的复杂性也要求我们在使用时保持谨慎,确保代码的可维护性和可读性。

相关推荐
木子.李3471 小时前
排序算法总结(C++)
c++·算法·排序算法
freyazzr2 小时前
C++八股 | Day2 | atom/函数指针/指针函数/struct、Class/静态局部变量、局部变量、全局变量/强制类型转换
c++
fpcc3 小时前
跟我学c++中级篇——理解类型推导和C++不同版本的支持
开发语言·c++
终焉代码4 小时前
STL解析——list的使用
开发语言·c++
DevangLic4 小时前
【 *p取出内容 &a得到地址】
c++
鑫鑫向栄4 小时前
[蓝桥杯]修改数组
数据结构·c++·算法·蓝桥杯·动态规划
鑫鑫向栄4 小时前
[蓝桥杯]带分数
数据结构·c++·算法·职场和发展·蓝桥杯
m0_552200825 小时前
《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》
c++·游戏·ue5
小wanga5 小时前
【递归、搜索与回溯】专题三 穷举vs暴搜vs回溯vs剪枝
c++·算法·机器学习·剪枝
Code_流苏6 小时前
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
开发语言·c++·stl容器·课设·期末大作业·日历程序·面向对象设计