资讯详情

资讯详情

C++中函数模板与类模板的简单使用及区别介绍

前言函数模板和类模板是 C 泛型编程的两块基石。很多教材把它们放在一起讲导致初学者产生一个模糊印象「模板就是让类型变成参数函数模板和类模板差不多」。实际上两者的使用方式、推导规则、特化能力和常见错误都有明显差别混淆它们会写出似是而非的代码。最典型的误解有三个以为函数模板必须显式指定模板实参其实大多数情况可以推导以为类模板也能像函数模板那样从构造参数推导类型C17 之前不行以为成员函数模板和类模板是一回事前者是类里的模板后者是模板定义的类。本文先分别给出两类模板的最小可用写法再从「类型推导、特化、定义位置、代码膨胀」四个维度对比最后用一个完整示例串起来。代码以 C17 为基准涉及 C17 的类模板实参推导class template argument deduction, CTAD会单独注明。一、函数模板让参数类型由实参推导函数模板的基本形式是template typename T 返回类型 函数名(参数列表)。调用时可以显式写fint(x)也可以让编译器从实参推导T。// 适用于 C17#include iostream#include string#include type_traitstemplate typename TT max_of(const T a, const T b) {return a b ? b : a;}// 两个参数类型不同时需要两个模板参数template typename T, typename Uauto add(T a, U b) - decltype(a b) {return a b;}int main() {std::cout max_of(3, 7) \n; // T 推导为 intstd::cout max_of(2.5, 1.5) \n; // T 推导为 doublestd::cout max_ofstd::string(a, b) \n; // 显式指定std::cout add(1, 2.5) \n; // Tint, Udoublereturn 0;}推导规则的核心是「按值传递会退化按引用传递保留 cv 和引用」。max_of(const T)传入数组时T会推导为数组类型而按值传递会退化成指针。这一点在写泛型代码时经常影响重载决议。函数模板可以被重载也可以和普通函数共存。重载决议时普通函数在参数完全匹配时优先于模板但如果模板能给出更精确的匹配模板也可能胜出。不要依赖这种微妙规则必要时显式指定模板实参或改函数名。函数模板还可以有默认模板实参C11 起也可以偏特化吗不能。函数模板不支持偏特化只支持全特化需要偏特化行为时通常改用类模板 静态成员函数或者用if constexprC17做编译期分支。二、类模板让整个类成为「类型工厂」类模板的形式是template typename T class/struct 名字 { ... };。使用时必须提供模板实参C17 之前// 适用于 C17#include iostream#include stdexcept#include vectortemplate typename Tclass Box {public:Box() default;explicit Box(T value) : value_(std::move(value)) {}T get() { return value_; }const T get() const { return value_; }void set(const T v) { value_ v; }private:T value_{};};template typename Tclass Stack {public:void push(const T v) { data_.push_back(v); }void pop() {if (data_.empty()) throw std::out_of_range(empty stack);data_.pop_back();}const T top() const {if (data_.empty()) throw std::out_of_range(empty stack);return data_.back();}bool empty() const { return data_.empty(); }private:std::vectorT data_;};int main() {Boxint b(42);std::cout b.get() \n;Stackstd::string s;s.push(hello);std::cout s.top() \n;// C17 起可以从构造实参推导Box b2(3.14); // Boxdoublestd::cout b2.get() \n;return 0;}类模板的成员函数只有在被使用时才会实例化对普通成员函数而言这意味着「某个成员函数对T不合法」不一定会立刻报错只有真正调用它时才会。这既是灵活性也是排错困难的来源。类模板支持全特化和偏特化。偏特化常用于针对指针、const、容器等形态给出不同实现// 适用于 C17template typename Tstruct TypeName { static const char* get() { return unknown; } };template struct TypeNameint { static const char* get() { return int; } };template typename Tstruct TypeNameT* { static const char* get() { return pointer; } };函数模板没有偏特化这是两者最实质的能力差异之一。三、四个维度的对比维度函数模板类模板声明形式template typename T T f(T)template typename T class C类型推导通常从函数实参自动推导C17 前必须显式写实参C17 起可 CTAD偏特化不支持支持全特化支持支持成员模板函数模板本身没有成员类内可再有成员函数模板定义位置通常放头文件通常放头文件除非显式实例化典型用途算法、工具函数容器、策略类、类型包装代码膨胀每个实例化组合生成一份每个实例化组合生成一份「定义必须放头文件」不是语法强制而是链接模型的结果编译器在实例化点需要看到完整定义否则只能生成一个未解析的符号。可以用显式实例化template class Stackint;把定义放到.cpp但必须提前知道所有用到的类型灵活性差。四、把两者组合起来实际代码里类模板经常包含函数模板成员函数模板又经常操作类模板对象// 适用于 C17#include iostream#include string#include utilitytemplate typename Tclass Container {public:explicit Container(T v) : value_(std::move(v)) {}// 成员函数模板允许用另一种类型赋值template typename Uvoid assign(U u) {value_ std::forwardU(u);}const T value() const { return value_; }private:T value_;};// 函数模板操作类模板template typename Tvoid print(const ContainerT c) {std::cout c.value() \n;}int main() {Containerstd::string c(hi);c.assign(hello); // U 推导为 const char()[6]print(c);Containerint n(1);n.assign(2.5); // int 被赋值为 2注意窄化print(n);return 0;}注意assign用完美转发时若U推导为int赋给std::string会编译失败模板的错误信息通常很长定位时要看「实例化回溯」的第一行而不是最后一行。n.assign(2.5)能编译但会把double窄化成int这是隐式转换而不是模板的锅写泛型接口时应考虑是否要static_assert限制类型。常见坑点❌ 把模板的定义放在.cpp调用方只看到声明链接时报未定义符号。✅ 定义放头文件或在.cpp里显式实例化所有需要的类型。❌ 对函数模板写偏特化编译器报「not a partial specialization」。✅ 函数模板只能全特化需要偏特化时改用类模板或if constexpr。❌template typename T T max(T a, T b)同时传int和double推导冲突。✅ 用两个模板参数或显式指定maxdouble(1, 2.5)或强制转换。❌ C17 之前写Box b(42);期望自动推导Boxint。✅ C17 前写Boxint b(42);CTAD 需要 C17 且构造函数可推导。❌ 在模板里对T做假定如T可比较直到某个实例化才报错。✅ 用static_assert或 C20 的 concepts 提前约束错误信息更清晰。❌ 以为类模板的每个成员函数都会立刻实例化结果漏掉某个类型不支持的操作。✅ 成员函数按需实例化给模板补一组针对目标类型的测试用例。❌ 模板参数按值传递大对象产生额外拷贝。✅ 只读参数用const T需要移动或转发时用Tstd::forward。❌ 两个不同源文件各自定义同名同签名的非内联函数模板ODR 冲突。✅ 模板定义放头文件并保持inline语义模板本身允许多个 TU 定义。总结对比点函数模板类模板实参推导默认支持C17 起支持 CTAD偏特化不支持支持成员无可有成员函数、成员类型、成员模板典型场景通用算法容器与策略封装定义位置头文件头文件或显式实例化函数模板解决「同一套逻辑适配多种参数类型」类模板解决「同一套结构适配多种元素类型」。记住三条就能避开大部分坑函数模板靠推导、类模板偏特化能力更强、模板定义通常必须对实例化点可见。把这些边界弄清楚再学 concepts、可变参数模板就会顺很多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →