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

C++泛型编程(二):现代C++特性

1.核心语言特性

1.auto类型推导

auto 关键字允许编译器自动推导变量的类型,简化了复杂类型的声明

// 基础类型推导
auto i = 42;              // 自动推导为 int
auto str = "hello";       // 自动推导为 const char*
auto pi = 3.14159;        // 自动推导为 double// 容器类型推导
std::vector<int> numbers{1, 2, 3};
auto iter = numbers.begin();  // 自动推导为 vector<int>::iterator// 在复杂类型中的应用
std::map<std::string, std::vector<int>> complex_map;
for (const auto& [key, value] : complex_map) {  // 结构化绑定配合 auto// 使用 key 和 value
}// 当类型名称冗长或复杂时使用
// 当类型显而易见时,可以显式声明
// 迭代器和 lambda 表达式时推荐使用

2.decltype类型推导

用于推导表达式的类型,常用于泛型编程和后置返回类型

// 基础用法
int x = 42;
decltype(x) y = 100;      // y 的类型与 x 相同// 在模板中的应用
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {  // 后置返回类型return t + u;
}// 与 auto 配合使用
template<typename Container>
auto getValue(Container& c) -> decltype(c.front()) {return c.front();
}// 用于需要依赖表达式类型的场景
// 在模板编程中推导返回类型
// 需要保持类型一致性时使用

3.lambda表达式

lambda 表达式提供了定义匿名函数对象的方式,使代码更加简洁和灵活

// 基本语法:[捕获列表](参数列表) -> 返回类型 { 函数体 }// 简单示例
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4) << std::endl;  // 输出:7// 捕获外部变量
int multiplier = 10;
auto multiply = [multiplier](int x) { return x * multiplier; };  // 值捕获
auto multiply_ref = [&multiplier](int x) { return x * multiplier; };  // 引用捕获// 在算法中的应用
std::vector<int> numbers = {3, 1, 4, 1, 5, 9};
std::sort(numbers.begin(), numbers.end(), [](int a, int b) { return a > b; });  // 自定义排序规则// 泛型 lambda(C++14)
auto generic_lambda = [](auto x, auto y) { return x + y; };// 短小的函数逻辑适合使用 lambda
// 注意捕获变量的生命周期
// 复杂逻辑建议使用命名函数

4.范围for循环

// 基础容器遍历
std::vector<int> nums = {1, 2, 3, 4, 5};
for (const auto& num : nums) {std::cout << num << " ";
}// 自定义类型遍历
class NumberRange {
public:NumberRange(int start, int end) : start_(start), end_(end) {}class iterator {// 迭代器实现};iterator begin() { return iterator(start_); }iterator end() { return iterator(end_); }
private:int start_, end_;
};// 使用
for (auto num : NumberRange(1, 5)) {std::cout << num << " ";
}

5.nullptr关键字

nullptr 是类型安全的指针空值常量,用来替代传统的 NULL 宏

// 更安全的指针空值表示
void* ptr = nullptr;  // 明确表示空指针
std::shared_ptr<int> smart_ptr = nullptr;  // 用于智能指针初始化// 函数重载场景
void process(int i) { std::cout << "整数版本" << std::endl; }
void process(int* p) { std::cout << "指针版本" << std::endl; }process(nullptr);  // 明确调用指针版本
// process(NULL);  // 可能导致编译错误,因为 NULL 可能被解释为 0// 完全替代 NULL 的使用
// 指针初始化时使用
// 在条件判断中使用 if (ptr != nullptr)

6.统一初始化语法

为解决初始化方式不统一隐式类型转换意外出错等问题,C++使用花括号 {} 提供了统一的初始化语法

// 以前的基本数据类型初始化
int a = 10; // 赋值初始化
int a(10); // 构造函数初始化// 以前的类类型初始化
std::string str("hello"); // 构造函数初始化
std::string str = "hello"; // 赋值初始化// 上述容易让开发者产生混淆// 统一后
// 基本类型初始化
int n{42};             // 直接初始化
std::string str{"hello"};  // 类对象初始化// 容器初始化
std::vector<int> vec{1, 2, 3, 4, 5};  // 列表初始化
std::map<std::string, int> map{{"one", 1},{"two", 2}
};// 自定义类的初始化
class Point {
public:Point(int x, int y) : x_{x}, y_{y} {}
private:int x_{0};  // 成员默认初始化int y_{0};
};Point p{10, 20};  // 统一初始化语法// 优先使用花括号初始化
// 注意防止窄化转换
// 类成员使用默认初始化

7.委托构造函数

允许构造函数调用同一个类的其他构造函数

class Person {
public:// 主构造函数Person(const std::string& name, int age, const std::string& address): name_(name), age_(age), address_(address) {}// 委托构造函数Person(const std::string& name, int age): Person(name, age, "未知") {}Person(const std::string& name): Person(name, 0) {}private:std::string name_;int age_;std::string address_;
};// 减少构造函数代码重复
// 保持初始化逻辑一致性
// 避免构造函数循环委托

8.右值引用和移动语义

两者提供了资源转移而非拷贝的能力

// 移动构造函数
class Buffer {
public:Buffer(size_t size) : data_(new char[size]), size_(size) {}// 移动构造函数Buffer(Buffer&& other) noexcept: data_(other.data_), size_(other.size_) {other.data_ = nullptr;other.size_ = 0;}// 移动赋值运算符Buffer& operator=(Buffer&& other) noexcept {if (this != &other) {delete[] data_;data_ = other.data_;size_ = other.size_;other.data_ = nullptr;other.size_ = 0;}return *this;}private:char* data_;size_t size_;
};// 使用示例
Buffer createBuffer(size_t size) {return Buffer(size);  // 返回时触发移动构造
}// 大对象传递时考虑使用移动语义
// 实现移动语义时注意处理原对象状态
// 移动构造函数和移动赋值运算符应声明为 noexcept

2.智能指针

1.定义与分类

C++中的一个类模板,它能够自动管理动态分配的内存,确保在适当的时候自动释放资源。它是RAII(资源获取即初始化)技术的一个具体实现

主要分类为:shared_ptr(共享式智能指针)、unique_ptr(独占式智能指针)、weak_ptr(弱引用智能指针)

2.shared_ptr

允许多个指针指向同一个对象。通过引用计数机制来管理对象的生命周期,当最后一个指向对象的shared_ptr被销毁时,对象会被自动删除

// 基本使用
shared_ptr<int> sp1 = make_shared<int>(10);
shared_ptr<int> sp2 = sp1;  // 引用计数+1// 引用计数
cout << sp1.use_count();  // 输出2// 自定义删除器
shared_ptr<int> sp3(new int[10], [](int* p) { delete[] p; });// 循环引用问题
class Node {shared_ptr<Node> next;  // 可能造成循环引用
};

3.unique_ptr

同一时刻只能有一个指针指向给定的对象。它对于被管理的对象具有唯一的所有权,不允许复制,但可以移动(std::move())

// 基本使用
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = make_unique<int>(20);  // 推荐方式// 所有权转移
unique_ptr<int> ptr3 = std::move(ptr1);  // ptr1变为nullptr// 不允许复制
// unique_ptr<int> ptr4 = ptr2;  // 编译错误// 自定义删除器
unique_ptr<FILE, decltype(&fclose)> fp(fopen("test.txt", "r"), fclose);

4.weak_ptr

是shared_ptr的助手,它指向shared_ptr管理的对象但不增加引用计数。主要用于解决shared_ptr循环引用的问题

void explainCircularReference() {// 1. 初始创建shared_ptr<Node> node1 = make_shared<Node>(1);  // node1引用计数=1shared_ptr<Node> node2 = make_shared<Node>(2);  // node2引用计数=1// 2. 建立相互引用node1->next = node2;  // node2引用计数+1=2// 因为现在有两个shared_ptr指向node2:// a. 原始的node2// b. node1->nextnode2->prev = node1;  // node1引用计数+1=2// 因为现在有两个shared_ptr指向node1:// a. 原始的node1// b. node2->prev// 3. 函数结束时// node1和node2的局部变量被销毁,各自引用计数-1// 但由于相互引用,引用计数仍然为1// node1被node2->prev引用// node2被node1->next引用
} // 引用计数永远不为0,无法释放内存// 解决方案:使用weak_ptr
class Node {
public:shared_ptr<Node> next;weak_ptr<Node> prev;  // 使用weak_ptrint data;Node(int val) : data(val) {}~Node() {cout << "Node " << data << " destroyed" << endl;}
};void demonstrateWeakPtr() {// 1. 创建节点shared_ptr<Node> node1 = make_shared<Node>(1);  // 引用计数=1shared_ptr<Node> node2 = make_shared<Node>(2);  // 引用计数=1// 2. 建立连接node1->next = node2;  // node2引用计数+1=2node2->prev = node1;  // 使用weak_ptr,node1引用计数不变=1// 3. 访问weak_ptr指向的对象if (auto ptr = node2->prev.lock()) {  // 检查weak_ptr是否有效cout << "Node2's prev points to: " << ptr->data << endl;}// 4. 函数结束时// node1的局部变量销毁,引用计数-1=0,node1被删除// node2的局部变量销毁,引用计数-1=1// node1被删除后,node2->prev自动失效// node2的引用计数最终变为0,也被删除
} // 两个节点都能正确释放

5.常用用法

// 容器使用
vector<shared_ptr<int>> vec;
vec.push_back(make_shared<int>(10));// 类成员
class Resource {unique_ptr<int> data;
public:Resource() : data(make_unique<int>(0)) {}
};// 函数返回
unique_ptr<int> createInt() {return make_unique<int>(42);
}

6.注意事项

// 避免原始指针转换
int* raw = new int(10);
shared_ptr<int> sp(raw);
// shared_ptr<int> sp2(raw);  // 危险:双重释放// 使用make_函数
auto sp = make_shared<int>(10);  // 比 shared_ptr<int>(new int(10)) 更安全// 及时释放
{shared_ptr<int> sp = make_shared<int>(42);
} // sp离开作用域自动释放

3.并发编程

1.std::thread

提供了创建和管理线程的标准方式

#include <thread>
#include <iostream>// 基本线程创建
void worker(int id) {std::cout << "线程 " << id << " 正在执行\n";
}int main() {std::thread t1(worker, 1);  // 创建线程std::thread t2([](){ std::cout << "Lambda线程正在执行\n"; });t1.join();  // 等待线程完成t2.join();return 0;
}

2.std::mutex

提供互斥锁机制,保护共享资源

#include <mutex>class ThreadSafeCounter {
public:void increment() {std::lock_guard<std::mutex> lock(mutex_);  // RAII方式加锁++count_;}int get() const {std::lock_guard<std::mutex> lock(mutex_);return count_;}private:mutable std::mutex mutex_;int count_{0};
};

3.std::condition_variable

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool done = false;void producer() {// 生产10个数据for (int i = 0; i < 10; ++i) {{   // 作用域锁std::lock_guard<std::mutex> lock(mtx);  // 自动加锁解锁data_queue.push(i);  // 将数据放入队列}   // 离开作用域,自动解锁cv.notify_one();  // 通知一个等待的消费者std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 模拟生产耗时}// 生产结束,标记完成{std::lock_guard<std::mutex> lock(mtx);done = true;  // 设置结束标志}cv.notify_all();  // 通知所有消费者,生产已结束
}// 消费者
void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);  // 可以手动解锁的锁// 等待条件:队列非空或生产结束cv.wait(lock, [] { return !data_queue.empty() || done; });// 如果生产结束且队列为空,退出循环if (done && data_queue.empty()) {break;}// 获取并处理数据int value = data_queue.front();data_queue.pop();lock.unlock();  // 提前解锁,让其他消费者能够访问队列std::cout << "消费: " << value << std::endl;}
}

4.std::future/promise

异步编程中的数据传递

std::promise<int> promise;
std::future<int> future = promise.get_future();// 异步任务
std::thread worker([&promise] {// 模拟耗时计算std::this_thread::sleep_for(std::chrono::seconds(2));promise.set_value(42);
});// 主线程等待结果
std::cout << "等待结果...\n";
std::cout << "结果: " << future.get() << std::endl;
worker.join();// 使用 async
auto future2 = std::async(std::launch::async, [] {std::this_thread::sleep_for(std::chrono::seconds(2));return 42;
});

4.标准库增加

1.std::tuple

处理异构数据集合

// 创建元组
auto student = std::make_tuple("张三", 20, 3.8);// 获取元素
std::cout << std::get<0>(student) << std::endl;  // 姓名
std::cout << std::get<1>(student) << std::endl;  // 年龄// 结构化绑定(C++17)
auto [name, age, gpa] = student;// 合并元组
auto info1 = std::make_tuple("张三", 20);
auto info2 = std::make_tuple(3.8, "计算机系");
auto full_info = std::tuple_cat(info1, info2);

2.std::optional

处理可能不存在的值

#include <optional>std::optional<std::string> getUserName(bool hasName) {if (hasName) {return "张三";}return std::nullopt;
}// 使用示例
auto name = getUserName(true);
if (name) {std::cout << "名字: " << *name << std::endl;
}// 提供默认值
std::cout << name.value_or("未知用户") << std::endl;

5.模板元编程

模板元编程是在编译期进行的计算和类型操作,主要用于:

  1. 编译期计算
  2. 类型转换和判断
  3. 代码生成

1.计算示例

// 1.编译期计算阶乘
template<unsigned N>
struct Factorial {static constexpr unsigned value = N * Factorial<N-1>::value;
};
// 递归终止
template<>
struct Factorial<0> {static constexpr unsigned value = 1;
};
// 使用
constexpr auto result = Factorial<5>::value;  // 5! = 120// 2.斐波那契数列
template<unsigned N>
struct Fibonacci {static constexpr unsigned value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};
template<>
struct Fibonacci<0> {static constexpr unsigned value = 0;
};
template<>
struct Fibonacci<1> {static constexpr unsigned value = 1;
};

2.类型操作

// 1.判断类型是否为指针
template<typename T>
struct IsPointer {static constexpr bool value = false;
};// 偏特化
template<typename T>
struct IsPointer<T*> {static constexpr bool value = true;
};// 2.移除引用
template<typename T>
struct RemoveReference {using type = T;
};template<typename T>
struct RemoveReference<T&> {using type = T;
};template<typename T>
struct RemoveReference<T&&> {using type = T;
};

3.注意事项

  1. 编译时间:复杂的模板元编程会增加编译时间、应适度使用,避免过度复杂化
  2. 可读性:模板元编程代码往往难以理解、需要良好的文档和注释
  3. 调试难度:编译期错误信息复杂、调试工具支持有限
  4. 维护成本:代码复杂度高、修改需要谨慎

6.性能优化

1. 移动语义

  • 通过移动而非拷贝来转移资源所有权
  • 避免不必要的深拷贝操作
  • 使用右值引用(&&)实现
class String {
public:// 移动构造函数String(String&& other) noexcept : data_(other.data_) {other.data_ = nullptr;  // 源对象置空}// 移动赋值运算符String& operator=(String&& other) noexcept {if (this != &other) {delete[] data_;data_ = other.data_;other.data_ = nullptr;}return *this;}private:char* data_;
};// 使用场景
String createString() {return String("hello");  // 自动触发移动 -- 使用右值版本:String(String&& other) noexcept
}
std::vector<String> vec;
vec.push_back(String("hello"));  // 移动而非拷贝

2.完美转发

  • 保持参数的值类别(左值/右值)
  • 通过 std::forward 实现
  • 用于泛型编程
template<typename T>
void wrapper(T&& param) { // 完美转发参数process(std::forward<T>(param));
}// 多参数版本
template<typename... Args>
void wrapper(Args&&... args) {process(std::forward<Args>(args)...);
}// 实现原理void wrapper(T&& param) // T&& 这里是通用引用,不是右值引用
// 使得当传入左值时,T 被推导为左值引用类型;当传入右值时,T 被推导为普通类型// 假设有个 int 类型变量 x
int x = 42;
wrapper(x);       // T 被推导为 int&,arg 类型为 int&
wrapper(42);      // T 被推导为 int,arg 类型为 int&&// 当 T 为 int& 时(传入左值):
// T&& => int& && => int&    // 折叠为左值引用
// 当 T 为 int 时(传入右值):
// T&& => int&& => int&&     // 保持为右值引用

3.CRTP

CRTP (Curiously Recurring Template Pattern,奇异递归模板模式) 主要用于实现静态多态

// 代码示例
// CRTP实现静态多态
template<typename Derived>
class Shape {
public:double area() {return static_cast<Derived*>(this)->computeArea();}
};class Circle : public Shape<Circle> {
public:Circle(double r) : radius(r) {}double computeArea() { return 3.14 * radius * radius; }
private:double radius;
};

通俗来讲,想象你在设计一个游戏,里面有不同类型的角色(战士、法师、弓箭手),他们都需要一个"攻击"的功能

// 传统方式 -- 虚函数
class 角色基类 {
public:virtual void 攻击() = 0;  // 每次调用都要查表,比较慢
};class 战士 : public 角色基类 {void 攻击() override { /* 挥剑 */ }
};// CRTP方式
template<typename 具体角色>
class 角色基类 {
public:void 攻击() {// 直接知道要调用哪个具体角色的攻击方法,不需要查表static_cast<具体角色*>(this)->具体攻击();}
};class 战士 : public 角色基类<战士> {
public:void 具体攻击() { /* 挥剑 */ }
};
// CRTP方式就像是提前做好了"攻击招式说明书",不用临时翻书查找,但是一旦写好就不能临时(动态)改变了

7.其他

本号文章仅为个人收集总结,强烈欢迎大佬与同好指误或讨论 ^^

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

相关文章:

  • 常见的降维算法
  • 采用SqlSugarClient创建数据库实例引发的异步调用问题
  • 【Qt/C++】深入理解 Lambda 表达式与 `mutable` 关键字的使用
  • MySQL的视图
  • AI 助力,轻松进行双语学术论文翻译!
  • C++GO语言微服务之gorm框架操作MySQL
  • uniapp使用ui.request 请求流式输出
  • LLaVA:开源多模态大语言模型深度解析
  • 物品识别 树莓派4 YOLO v11
  • 青少年编程与数学 02-019 Rust 编程基础 05课题、复合数据类型
  • 解锁 DevOps 新境界 :使用 Flux 进行 GitOps 现场演示 – 自动化您的 Kubernetes 部署
  • 大模型(LLMs)强化学习——RLHF及其变种
  • 基于强化学习 Q-learning 算法求解城市场景下无人机三维路径规划研究,提供完整MATLAB代码
  • linux测试硬盘读写速度
  • uniapp|实现商品分类与列表数据联动,左侧菜单右侧商品列表(瀑布流、高度自动计算、多端兼容)
  • 音频类网站或者资讯总结
  • 电子电器架构 --- 车载以太网拓扑
  • OSPF的四种特殊区域(Stub、Totally Stub、NSSA、Totally NSSA)详解
  • PyTorch 线性回归模型构建与神经网络基础要点解析
  • 数据结构精解:优先队列、哈希表与树结构
  • AI 入门资源:微软 AI-For-Beginners 项目指南
  • Kotlin 协程 vs RxJava vs 线程池:性能与场景对比
  • 【论文阅读】Efficient and secure federated learning against backdoor attacks
  • MySQL 索引(一)
  • 【C++ Qt】容器类(GroupBox、TabWidget)内附思维导图 通俗易懂
  • 发行基础:本地化BUG导致审核失败
  • 动态规划:最长递增子序列
  • 通俗的桥接模式
  • Kubernetes生产实战(十七):负载均衡流量分发管理实战指南
  • 第三天——贪心算法——区间问题