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引用计数=1
    shared_ptr<Node> node2 = make_shared<Node>(2);  // node2引用计数=1

    // 2. 建立相互引用
    node1->next = node2;  // node2引用计数+1=2
                         // 因为现在有两个shared_ptr指向node2:
                         // a. 原始的node2
                         // b. node1->next

    node2->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_ptr
    int data;

    Node(int val) : data(val) {}
    ~Node() {
        cout << "Node " << data << " destroyed" << endl;
    }
};

void demonstrateWeakPtr() {
    // 1. 创建节点
    shared_ptr<Node> node1 = make_shared<Node>(1);  // 引用计数=1
    shared_ptr<Node> node2 = make_shared<Node>(2);  // 引用计数=1

    // 2. 建立连接
    node1->next = node2;  // node2引用计数+1=2
    node2->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.其他

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值