当前位置: 首页 > news >正文

C++入门自学Day17-- 模版进阶知识

前言

        C++ 的两个强大且常被考查的主题就是 模板(templates) 和 继承/多态(inheritance & polymorphism)。模板把“泛型”和“编译期多态”带入语言,允许你写出与类型无关但高效的代码;继承把“运行时多态”带入语言,方便用公共接口操纵不同实现。

        本文按教学博客的风格来写:先给出模板函数的使用与进阶技巧(含现代 C++ 的常见高级手段),再讲 C++ 的继承特性(含虚函数、对象切片、虚继承等),每部分配上示例、常见陷阱与最佳实践,最后做统一总结。示例尽量贴近工程实战,适合直接放在博客里。


主要内容简介

  • 1、模板函数基础:函数模板、类型推导、非类型模板参数、模板重载/特化

  • 2、模板进阶:完美转发、折叠表达式、变参模板、SFINAE/enable_if、std::void_t 检测、C++20 Concepts

  • 3、继承基础:单继承、派生类、访问控制、构造/析构顺序、切片问题

  • 4、运行时多态:虚函数、纯虚函数(抽象类)、虚析构函数、override/final、dynamic_cast

  • 5、多重继承与虚继承:菱形问题、虚基类、二义性解决

  • 6、模板与继承的交互:CRTP、用模板约束继承关系(is_base_of / concepts)

  • 7、常见坑 & 最佳实践总结


一、C++ 模板函数(从基础到进阶)

1.1 函数模板 — 基本用法

template<typename T>
T Add(T a, T b) {return a + b;
}int x = Add<int>(1, 2); // 显示指定
auto y = Add(1, 2);     // 编译器推导为 int

要点:

  • 模板定义通常放在头文件(因为编译器需要在使用点见到定义进行实例化)。

  • 支持显式模板参数或由编译器类型推导

  • 函数模板可以与普通函数重载共存,重载解析遵循普通函数重载规则 + 模板优先级。


1.2 非类型模板参数 & 模板重载/特化

template<typename T, int N>
class Array{
private: T _a[N];
};int main(){Array<int, 1000> a;
}
  • 非类型模板参数(如 N)允许在编译期传入常量。

  • 函数模板可以做完整特化(rare),但不能做部分特化。部分特化只适用于类模板。

  •  可以作为非类型模板参数的类型

  • 整数类型(int, char, bool, 枚举等)

  • 指针或引用(必须是指向具有静态存储期的对象或函数的常量指针/引用)

  • std::nullptr_t

  • 注意:

  • 浮点型及自定义类型无法作为非类型模版参数

模版的特化(针对某些类型进行特殊化处理):
在函数模板显式特化时,函数签名必须和主模板一致,只有模板参数的具体化不同,否则不是有效的特化。
template<typename T>
bool IsEqual(const T& left , const T& right){return left == right;
}
// 模版的特化(特殊化处理)
template<>
bool IsEqual<char*>(char* const& left , char* const& right){ return (strcmp(left, right) == 0);
}int main(){int a = 0, b = 1;cout << IsEqual(a,b)<<endl;const char* p1 = "Hello";const char* p2 = "World";cout<< IsEqual(p1,p2) << endl;
}

输出描述:

0

0


特化的分类:1、全特化。2、偏特化

template<class T1, class T2>
class Data{public:Data(){cout<< "Data<T1,T2>"<<endl;}private:T1 _d1;T2 _d2;
};
template<>
class Data<int, char>{public:Data(){cout<< "全特化 Data<int,char>"<<endl;}private:;
};
template<class T2>
class Data<int,T2>{public:Data(){cout<<"偏特化 Data<int,T2>"<<endl;}private:;
};
int main(){Data<short, short> d1;Data<int, char> d2;Data<int, double> d3;
}

输出描述:

Data<T1,T2>
全特化 Data<int,char>
偏特化 Data<int,T2>


1.3 变参模板(variadic templates)与折叠表达式(C++17)

// 递归版本(老式)
template<typename T>
T Sum(T v){ return v; }template<typename T, typename... Ts>
T Sum(T head, Ts... tail) {return head + Sum(tail...);
}// C++17 折叠表达式,简洁高效
template<typename... Ts>
auto Sum2(Ts... args) {return (args + ... + 0); // fold expression
}

1.4 转发引用与完美转发(perfect forwarding)

用于实现通用工厂/封装函数,避免不必要拷贝:

template<typename T, typename... Args>
std::unique_ptr<T> MakeUnique(Args&&... args) {return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

注意:

  • T&& 在模板上下文中可能是 转发引用(又称万能引用)。

  • 使用 std::forward 将参数按原始值类别(左值/右值)传递,保留移动语义。


二、C++ 模板的分离编译

🔹 1. 模板的基本编译机制

  • 普通函数 / 类:编译器在编译 .cpp 文件时就能生成确定的符号。

  • 模板函数 / 类:只有在**实例化(instantiation)**时(即编译器遇到具体类型实参)才会生成代码。

👉 因此模板代码必须在编译时可见,这就是为什么我们通常把模板的实现,函数,类的声明都写在 .h 文件里,而不是 .cpp。


🔹 2. 为什么模板不能直接“分离编译”

假设你写了两个文件:

// MyTemplate.h
template<typename T>
T add(T a, T b);
// MyTemplate.cpp
#include "MyTemplate.h"
template<typename T>
T add(T a, T b) {return a + b;
}

然后在 main.cpp 里:

#include "MyTemplate.h"
int main() {int x = add(1, 2);  // 想用 int 版本
}

⚠️ 这会导致 链接错误:因为 main.cpp 编译时看到 add 的声明,但 .o 文件里没生成 add<int> 的实例化。

因为mytemplate.cpp 中函数模版并不知道其对应类型。实例化是在main.cpp

编译器不会自动去 MyTemplate.cpp 里“搜模板定义”。


🔹 3. 解决方法

有三种常见方法:

✅ 方法一:全写在头文件里(最常见)
// MyTemplate.h
template<typename T>
T add(T a, T b) {return a + b;
}

这样所有用到 add 的翻译单元都能看到实现,编译器就能实例化。


✅ 方法二:显式实例化(explicit instantiation)

你可以在 .cpp 里告诉编译器:

“我需要这些具体类型的实例化,帮我生成一次即可”。

// MyTemplate.h
template<typename T>
T add(T a, T b);
// MyTemplate.cpp
#include "MyTemplate.h"template<typename T>
T add(T a, T b) {return a + b;
}// 显式实例化
template int add<int>(int, int);
template double add<double>(double, double);

这样 main.cpp 里用 add<int> 或 add<double> 时,链接器能找到在 MyTemplate.o 里已经生成的符号。

👉 适合库开发,不想把实现暴露在头文件时。


✅ 方法三:把实现放在 .tpp 或 .inl 文件中

约定俗成的做法:

  • .h 里只放声明

  • .tpp(或 .inl)里放实现

  • 在 .h 文件末尾 #include "MyTemplate.tpp"


这样使用者只需要 #include "MyTemplate.h",实现还是“逻辑分离”的。

  • 模板必须在实例化时可见,所以不能像普通函数那样分离编译。

  • 常见解决办法:

    1. 全部写在 .h(最常见)。

    2. 在 .cpp 中显式实例化需要的类型。

    3. .h + .tpp 组合,让逻辑结构更清晰。


三、总结

  • 模板:提供“编译期泛型”与静态多态。掌握变参模板、完美转发、SFINAE(或 Concepts)、std::void_t 检测,是写出高质量模板库的关键。优先用 Concepts(C++20)以获得更可读的接口约束。

http://www.xdnf.cn/news/1429687.html

相关文章:

  • [re_1] const|cap|zookper|snowflake
  • maven私有仓库配置
  • 【linux】firewall防火墙
  • 急招 MySQL / PG DBA,欢迎自荐或推荐朋友!推荐有奖!
  • Delphi 5 操作Word表格选区问题解析
  • 玩转Docker | 使用Docker部署Haptic笔记管理应用
  • Resemble Enhance:AI语音增强技术的革新之作
  • Rsync + Rsyncd 从入门到项目实战:自动化备份全攻略
  • 阅读Linux 4.0内核RMAP机制的代码,画出父子进程之间VMA、AVC、anon_vma和page等数据结构之间的关系图。
  • innovus: postRoute如何加shielding
  • ARM - GPIO 标准库开发
  • 【Python3教程】Python3高级篇之XML解析
  • 3dmax烘培插件3dmax法线贴图烘焙教程glb和gltf元宇宙灯光效果图烘焙烘焙光影贴图支持VR渲染器
  • 10 51单片机之DS1302实时时钟
  • Java集合源码解析之ArrayList
  • 网络共享协议
  • 【Vue2 ✨】 Vue2 入门之旅(五):组件化开发
  • 车载刷写架构 --- ECU软件更新怎么保证数据的正确性?
  • MATLAB矩阵及其运算(三)矩阵的创建
  • 应用层:HTTP/HTTPS协议
  • 【Python数据可视化:Matplotlib高级技巧】
  • 高效数据传输的秘密武器:Protobuf
  • 京东商品详情商品详情接口技术实现:从数据抓取到结构化解析全方案
  • LeetCode 777.在LR字符串中交换相邻字符
  • C++ 面试高频考点 力扣 852. 山脉数组的峰顶索引 二分查找 题解 每日一题
  • 【Linux笔记】命令行与vim基础
  • 单元测试总结2
  • MTK Linux DRM分析(二十六)- MTK mtk_drm_ddp_xxx.c
  • Spring Boot 3.5.3 集成 Log4j2 日志系统
  • 从spring MVC角度理解HTTP协议及Request-Response模式