【QT】现代C++结合:从新手到高手的编程魔法之旅

在这里插入图片描述

个人主页:Air
归属专栏:QT

在这里插入图片描述

正文

1 引言:为什么现代C++像瑞士军刀?

还记得你第一次看到瑞士军刀时的惊讶吗?那么小的一个工具,竟然包含了刀片、剪刀、螺丝刀、开瓶器等等无数功能。现代C++给我的感觉正是如此——一个看似简单的编程语言,却蕴含着解决各种复杂问题的强大能力。

C++自1985年诞生以来,已经走过了漫长的道路。从最初的"C with Classes"到如今的C++20标准,这门语言经历了脱胎换骨的变化。现代C++(通常指C++11及之后版本)不再是那个令人望而生畏的"复杂语言",而是一个既强大又优雅的工具集。

你可能会问,为什么在Python、JavaScript等语言大行其道的今天,还要学习C++?答案很简单:性能和控制力。当你需要榨干硬件每一分性能时,当你需要精确控制内存布局时,当你开发游戏引擎、操作系统或高频交易系统时,C++仍然是无可替代的选择。

更重要的是,现代C++让这一切变得比以前简单多了。auto关键字、智能指针、lambda表达式等特性大大减少了样板代码,让我们能更专注于问题本身而非语言细节。

在这趟旅程中,我将带你探索现代C++最强大的特性,并通过大量代码示例、图表和实际应用场景,帮助你掌握这门艺术的精髓。准备好了吗?让我们开始吧!

2 C++进化简史:从C++98到C++20

2.1 远古时代:C++98/03

让我们把时间拨回到1998年,那一年C++第一个国际标准诞生了。这时的C++已经具备了类、模板、异常处理等现代特性,但用今天的眼光看,它还是显得有些"原始"。

// 典型的C++98代码
#include <vector>
#include <iostream>

class MyVector {
public:
    MyVector(int size) : data(new int[size]), size(size) {}
    ~MyVector() { delete[] data; } // 必须手动管理内存!
    
    int& operator[](int index) { return data[index]; }
    
private:
    int* data;
    int size;
};

int main() {
    std::vector<int> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }
    
    // 迭代器声明很冗长
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    
    return 0;
}

那时的程序员需要手动管理内存,编写大量的样板代码,迭代器声明冗长,而且缺乏很多现代编程范式所需的工具。

2.2 革命性的C++11:现代C++的诞生

2011年发布的C++11标准是一次真正的革命,以至于许多人称其为"现代C++"的开端。这一次更新引入了如此多的新特性,几乎像是一门新语言。

// 同样的功能,C++11实现
#include <vector>
#include <iostream>
#include <memory>

// 使用智能指针自动管理内存
class MyVector {
public:
    MyVector(int size) : data(std::make_unique<int[]>(size)), size(size) {}
    // 不需要手动写析构函数!
    
    int& operator[](int index) { return data[index]; }
    
private:
    std::unique_ptr<int[]> data;
    int size;
};

int main() {
    std::vector<int> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }
    
    // auto关键字简化迭代器声明
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    
    // 范围for循环更简洁
    for (int value : vec) {
        std::cout << value << " ";
    }
    
    // Lambda表达式!
    std::for_each(vec.begin(), vec.end(), [](int x) {
        std::cout << x << " ";
    });
    
    return 0;
}

C++11引入了auto关键字、智能指针、lambda表达式、范围for循环、移动语义等改变游戏规则的特性,彻底改变了C++的编程方式。

2.3 持续进化:C++14/17/20

C++14和C++17进一步 refine 了C++11的特性,添加了一些小而实用的功能。而C++20则带来了又一次重大飞跃,引入了概念(concepts)、范围视图(ranges)、协程(coroutines)等强大特性。

// C++20 示例
#include <iostream>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 使用范围视图过滤和转换数据
    auto even_squares = numbers 
        | std::views::filter([](int x) { return x % 2 == 0; })
        | std::views::transform([](int x) { return x * x; });
    
    for (auto x : even_squares) {
        std::cout << x << " "; // 输出: 4 16 36 64 100
    }
    
    return 0;
}

这个简单的例子展示了C++20范围视图的强大能力——我们可以用声明式的方式处理数据,代码既简洁又表达力强。

下面是C++标准演进的主要时间线:

timeline
    title C++标准演进时间线
    section 1998
        C++98 : 第一个国际标准
    section 2003
        C++03 : 小幅修订
    section 2011
        C++11 : 重大更新<br>现代C++起点
    section 2014
        C++14 : 小幅改进
    section 2017
        C++17 : 实用特性增强
    section 2020
        C++20 : 又一次重大飞跃
    section 2023
        C++23 : 即将到来...

【举例】
想象一下,C++的演进就像汽车的进化。C++98是一辆老式汽车,能开但缺少现代便利设施;C++11是加入了自动变速、动力转向和空调的现代汽车;而C++20则像是配备了自动驾驶和智能导航系统的未来汽车。

3 自动类型推导:让编译器为你工作

3.1 auto关键字:少打字,多做事

auto关键字是C++11最受欢迎的特性之一。它让编译器根据初始化表达式自动推导变量类型,大大减少了代码冗余。

// 没有auto的时代
std::vector<std::pair<int, std::string>>::iterator it = my_vector.begin();

// 有auto的时代
auto it = my_vector.begin(); // 编译器知道it的类型

但auto不只是为了少打几个字,它还能让代码更灵活、更容易维护:

#include <vector>
#include <string>
#include <iostream>

int main() {
    std::vector<std::string> messages = {"Hello", "World", "C++"};
    
    // 如果后面改变了容器类型,只需要修改一处
    for (auto it = messages.begin(); it != messages.end(); ++it) {
        std::cout << *it << " ";
    }
    
    // 范围for循环结合auto
    for (const auto& message : messages) {
        std::cout << message << " ";
    }
    
    // auto与函数返回类型结合
    auto result = []() { return 42; }; // result类型是int
    
    return 0;
}

3.2 decltype和decltype(auto):精确的类型控制

有时候我们需要更精确地控制类型推导,这时候decltype就派上用场了。decltype可以获取表达式的确切类型,包括const和引用限定符。

#include <iostream>

int main() {
    int x = 10;
    const int y = 20;
    int& z = x;
    
    decltype(x) a = x;        // a是int
    decltype(y) b = y;        // b是const int
    decltype(z) c = z;        // c是int&
    decltype((x)) d = x;      // d是int&,注意双括号的差异!
    
    // decltype常用于模板编程
    auto add = [](auto a, auto b) -> decltype(a + b) {
        return a + b;
    };
    
    std::cout << add(1, 2) << std::endl;       // 3
    std::cout << add(1.5, 2.3) << std::endl;   // 3.8
    
    return 0;
}

C++14引入了decltype(auto),它结合了auto的便利性和decltype的精确性:

int& get_reference();
const int& get_const_reference();

int main() {
    auto a = get_reference();          // a是int(值拷贝)
    decltype(auto) b = get_reference(); // b是int&
    
    auto c = get_const_reference();          // c是int(值拷贝)
    decltype(auto) d = get_const_reference(); // d是const int&
    
    return 0;
}

3.3 类型推导的最佳实践

虽然auto很强大,但需要明智地使用。以下是一些最佳实践:

#include <vector>
#include <memory>

class Widget {
public:
    Widget() = default;
    // ... 其他成员函数
};

int main() {
    // 好的使用场景
    auto widget = std::make_unique<Widget>(); // 避免重复类型
    auto counter = 0;                         // 明显的基本类型
    
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (const auto& num : numbers) {         // 避免不必要的拷贝
        // 处理num
    }
    
    // 不好的使用场景
    auto result = get_some_result();          // 类型不明确,可读性差
    
    // 应该明确类型的情况
    int explicit_value = 10;                  // 明确表示是int
    std::vector<int>::size_type size = numbers.size(); // 确保正确类型
    
    return 0;
}

类型推导的决策过程可以这样理解:

需要声明变量
类型明显且简单?
使用具体类型
如 int, string
需要引用或const?
使用auto&或const auto&
使用auto
完成声明

【举例】
使用auto就像是在餐厅点菜时说"给我来一份和那位顾客一样的",而不需要详细描述每道菜。既方便又不容易出错,除非你完全不知道会得到什么(这时候就应该明确指定类型)。

4 智能指针:告别内存泄漏的噩梦

4.1 unique_ptr:独占所有权的智慧

std::unique_ptr是C++11引入的智能指针,它代表对动态分配对象的独占所有权。当unique_ptr离开作用域时,它会自动删除所拥有的对象。

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    void do_something() { std::cout << "Doing something\n"; }
};

int main() {
    // 创建unique_ptr
    std::unique_ptr<Resource> res1 = std::make_unique<Resource>();
    
    // 使用箭头运算符访问成员
    res1->do_something();
    
    // 使用get()获取原始指针(谨慎使用!)
    Resource* raw_ptr = res1.get();
    
    {
        // 转移所有权(移动语义)
        std::unique_ptr<Resource> res2 = std::move(res1);
        
        if (!res1) {
            std::cout << "res1 is now null\n";
        }
        
        res2->do_something();
        // res2离开作用域,资源被自动释放
    }
    
    // 此时资源已被释放,不需要手动delete
    
    return 0;
}

unique_ptr是零开销抽象——它的性能与原始指针相同,但提供了自动内存管理的好处。

4.2 shared_ptr和weak_ptr:共享所有权与观察者

当需要共享所有权时,std::shared_ptr就派上用场了。它使用引用计数来跟踪有多少个shared_ptr指向同一对象。

#include <iostream>
#include <memory>

class SharedResource {
public:
    SharedResource() { std::cout << "SharedResource created\n"; }
    ~SharedResource() { std::cout << "SharedResource destroyed\n"; }
};

int main() {
    // 创建shared_ptr
    std::shared_ptr<SharedResource> ptr1 = std::make_shared<SharedResource>();
    
    {
        // 共享所有权
        std::shared_ptr<SharedResource> ptr2 = ptr1;
        std::cout << "Use count: " << ptr1.use_count() << std::endl; // 2
        
        {
            std::shared_ptr<SharedResource> ptr3 = ptr1;
            std::cout << "Use count: " << ptr1.use_count() << std::endl; // 3
        } // ptr3离开作用域,引用计数减1
        
        std::cout << "Use count: " << ptr1.use_count() << std::endl; // 2
    } // ptr2离开作用域,引用计数减1
    
    std::cout << "Use count: " << ptr1.use_count() << std::endl; // 1
    // ptr1离开作用域,引用计数为0,资源被释放
    
    return 0;
}

但shared_ptr有一个潜在问题:循环引用。这时候就需要std::weak_ptr:

#include <iostream>
#include <memory>

class Node {
public:
    std::string name;
    std::shared_ptr<Node> partner;
    std::weak_ptr<Node> weak_partner; // 使用weak_ptr避免循环引用
    
    Node(const std::string& n) : name(n) {
        std::cout << "Node " << name << " created\n";
    }
    
    ~Node() {
        std::cout << "Node " << name << " destroyed\n";
    }
};

int main() {
    // 创建循环引用
    auto node1 = std::make_shared<Node>("A");
    auto node2 = std::make_shared<Node>("B");
    
    node1->partner = node2; // shared_ptr循环引用
    node2->partner = node1; // 会导致内存泄漏!
    
    // 正确的方式:使用weak_ptr
    auto node3 = std::make_shared<Node>("C");
    auto node4 = std::make_shared<Node>("D");
    
    node3->weak_partner = node4;
    node4->weak_partner = node3; // 不会导致循环引用
    
    // 使用weak_ptr
    if (auto locked = node3->weak_partner.lock()) {
        std::cout << "Accessing " << locked->name << " safely\n";
    } else {
        std::cout << "Object already destroyed\n";
    }
    
    return 0;
}

4.3 智能指针的最佳实践

智能指针很强大,但需要正确使用:

#include <memory>
#include <vector>

class Object {
public:
    Object() = default;
};

// 工厂函数返回unique_ptr
std::unique_ptr<Object> create_object() {
    return std::make_unique<Object>();
}

void process_object(const std::shared_ptr<Object>& obj) {
    // 接受共享所有权
}

int main() {
    // 优先使用make_unique和make_shared
    auto ptr1 = std::make_unique<Object>(); // 而不是new Object
    auto ptr2 = std::make_shared<Object>(); // 而不是shared_ptr<Object>(new Object)
    
    // make_shared更高效,因为它一次性分配内存存储对象和引用计数
    
    // 在容器中存储智能指针
    std::vector<std::shared_ptr<Object>> objects;
    objects.push_back(std::make_shared<Object>());
    objects.push_back(std::make_shared<Object>());
    
    // 根据所有权语义选择智能指针
    // 独占所有权:unique_ptr
    // 共享所有权:shared_ptr
    // 观察而不拥有:weak_ptr
    
    // 不要混合使用原始指针和智能指针
    Object* raw_ptr = ptr1.get();
    // std::shared_ptr<Object> bad_idea(raw_ptr); // 错误!会导致多次删除
    
    return 0;
}

智能指针的选择可以遵循以下决策流程:

需要动态分配对象
所有权模式
独占所有权
使用unique_ptr
共享所有权
使用shared_ptr
仅观察不拥有
使用weak_ptr
需要转换为共享所有权?
使用std::move转换为shared_ptr
保持unique_ptr
需要避免循环引用?
在适当位置使用weak_ptr
保持shared_ptr

【举例】
智能指针就像图书馆的借书系统:unique_ptr像是一次只能借给一个人的珍本书;shared_ptr像是可以借给多个人的普通书(图书馆会记录有多少人借了这本书);weak_ptr像是图书目录,你可以查看书的信息,但不能直接阅读,需要先"借阅"(lock())成shared_ptr。

5 Lambda表达式:匿名函数的艺术

5.1 Lambda基础:从函数对象到Lambda

在C++11之前,我们需要创建函数对象(仿函数)来传递自定义行为:

// C++98风格的函数对象
struct AddFunctor {
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    AddFunctor add;
    int result = add(3, 4); // 7
    return 0;
}

Lambda表达式让这一切变得简单:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    // 最简单的lambda:[](){}
    auto basic = []() { std::cout << "Hello Lambda!\n"; };
    basic();
    
    // 带参数的lambda
    auto add = [](int a, int b) { return a + b; };
    std::cout << add(3, 4) << std::endl; // 7
    
    // 在算法中使用lambda
    std::vector<int> numbers = {5, 2, 8, 1, 9};
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a < b; // 升序排序
    });
    
    for (int num : numbers) {
        std::cout << num << " "; // 1 2 5 8 9
    }
    std::cout << std::endl;
    
    return 0;
}

5.2 捕获列表:与外部世界的交互

Lambda的真正强大之处在于它能捕获外部变量:

#include <iostream>

int main() {
    int x = 10;
    int y = 20;
    
    // 值捕获
    auto value_capture = [x]() {
        std::cout << "Value capture: " << x << std::endl;
        // x++; // 错误:x是只读的
    };
    value_capture();
    
    // 引用捕获
    auto reference_capture = [&y]() {
        std::cout << "Reference capture: " << y << std::endl;
        y++; // 修改外部变量
    };
    reference_capture();
    std::cout << "y after modification: " << y << std::endl; // 21
    
    // 隐式捕获
    auto implicit_capture_by_value = [=]() { // 以值方式捕获所有外部变量
        std::cout << "Implicit capture: " << x << ", " << y << std::endl;
    };
    
    auto implicit_capture_by_ref = [&]() { // 以引用方式捕获所有外部变量
        std::cout << "Implicit capture: " << x << ", " << y << std::endl;
        y++; // 可以修改
    };
    
    // 混合捕获
    auto mixed_capture = [=, &y]() { // 默认值捕获,但y是引用捕获
        std::cout << "Mixed capture: " << x << ", " << y << std::endl;
        y++; // 可以修改y
    };
    
    // 初始化捕获(C++14)
    auto init_capture = [z = x + y]() { // 创建新变量z
        std::cout << "Init capture: " << z << std::endl;
    };
    init_capture();
    
    return 0;
}

5.3 通用Lambda和模板Lambda(C++14/20)

C++14引入了通用lambda,可以接受任意类型的参数:

#include <iostream>
#include <vector>
#include <string>

int main() {
    // 通用lambda:使用auto参数
    auto print = [](const auto& arg) {
        std::cout << arg << std::endl;
    };
    
    print(42);           // int
    print(3.14);         // double
    print("Hello");      // const char*
    
    // 多个auto参数
    auto add = [](auto a, auto b) {
        return a + b;
    };
    
    std::cout << add(1, 2) << std::endl;        // 3
    std::cout << add(1.5, 2.3) << std::endl;    // 3.8
    std::cout << add(std::string("Hello"), std::string(" World")) << std::endl; // Hello World
    
    // C++20模板lambda
    auto template_lambda = []<typename T>(const std::vector<T>& vec) {
        for (const auto& item : vec) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    };
    
    std::vector<int> int_vec = {1, 2, 3};
    std::vector<std::string> str_vec = {"A", "B", "C"};
    
    template_lambda(int_vec);  // 1 2 3
    template_lambda(str_vec);  // A B C
    
    return 0;
}

Lambda表达式在现代C++编程中无处不在,特别是在STL算法、异步编程和回调函数中。

需要函数对象
需要捕获外部变量?
使用普通函数或函数指针
如何捕获?
值捕获
使用 = 或具体变量名
引用捕获
使用 & 或 &变量名
混合捕获
=, &x 或 &, x
需要修改捕获的变量?
添加mutable关键字
保持默认const
lambda定义
需要模板参数?
C++20模板lambda
使用auto参数C++14通用lambda

【举例】
Lambda就像是一个快餐店的定制订单。捕获列表是你选择的配料(哪些外部变量可用),参数列表是调味品(调用时传递的参数),函数体是烹饪方法。你可以根据需要定制自己的"餐点",而不必去完整的餐厅(定义完整的函数对象)。

6 移动语义和完美转发:高效资源管理

6.1 左值、右值和将亡值

理解移动语义首先需要理解C++中的值类别:

#include <iostream>
#include <utility>

void process_value(int& val) {
    std::cout << "Lvalue: " << val << std::endl;
}

void process_value(int&& val) {
    std::cout << "Rvalue: " << val << std::endl;
}

int main() {
    int a = 42; // a是左值
    int b = a;  // a是左值,可以出现在赋值左边
    
    process_value(a);        // 调用左值重载
    process_value(100);      // 调用右值重载
    process_value(a + b);    // 调用右值重载
    process_value(std::move(a)); // 调用右值重载
    
    // 将亡值(xvalue)示例
    int&& rref = 100;        // 右值引用
    process_value(std::move(rref)); // 将亡值
    
    return 0;
}

6.2 移动语义:避免不必要的拷贝

移动语义允许我们将资源从一个对象"移动"到另一个对象,而不是进行昂贵的拷贝:

#include <iostream>
#include <cstring>

class String {
public:
    // 构造函数
    String(const char* str = "") {
        size = strlen(str);
        data = new char[size + 1];
        strcpy(data, str);
        std::cout << "Constructed: " << data << std::endl;
    }
    
    // 拷贝构造函数
    String(const String& other) {
        size = other.size;
        data = new char[size + 1];
        strcpy(data, other.data);
        std::cout << "Copied: " << data << std::endl;
    }
    
    // 移动构造函数
    String(String&& other) noexcept {
        data = other.data;   // 窃取资源
        size = other.size;
        other.data = nullptr; // 置空原对象
        other.size = 0;
        std::cout << "Moved: " << data << std::endl;
    }
    
    // 移动赋值运算符
    String& operator=(String&& other) noexcept {
        if (this != &other) {
            delete[] data;   // 释放现有资源
            
            data = other.data; // 窃取资源
            size = other.size;
            
            other.data = nullptr;
            other.size = 0;
            
            std::cout << "Move assigned: " << data << std::endl;
        }
        return *this;
    }
    
    ~String() {
        if (data) {
            std::cout << "Destroying: " << data << std::endl;
        } else {
            std::cout << "Destroying moved-from object" << std::endl;
        }
        delete[] data;
    }
    
private:
    char* data;
    size_t size;
};

String create_string() {
    return String("Hello World"); // 可能触发移动语义
}

int main() {
    std::cout << "=== 创建原始字符串 ===\n";
    String s1 = "Hello";
    
    std::cout << "\n=== 拷贝构造 ===\n";
    String s2 = s1; // 调用拷贝构造函数
    
    std::cout << "\n=== 移动构造 ===\n";
    String s3 = std::move(s1); // 调用移动构造函数
    
    std::cout << "\n=== 函数返回 ===\n";
    String s4 = create_string(); // 可能使用移动或拷贝省略
    
    std::cout << "\n=== 移动赋值 ===\n";
    s4 = String("New String"); // 调用移动赋值运算符
    
    std::cout << "\n=== 结束 ===\n";
    return 0;
}

6.3 完美转发:保持值类别的转发

完美转发允许我们在模板函数中将参数以其原始的值类别(左值或右值)转发给其他函数:

#include <iostream>
#include <utility>

void process(int& x) {
    std::cout << "Lvalue: " << x << std::endl;
}

void process(int&& x) {
    std::cout << "Rvalue: " << x << std::endl;
}

// 不完美转发
template<typename T>
void forward_not_perfect(T arg) {
    process(arg); // 总是调用左值重载
}

// 完美转发
template<typename T>
void forward_perfect(T&& arg) {
    process(std::forward<T>(arg)); // 保持值类别
}

int main() {
    int x = 42;
    
    std::cout << "不完美转发:\n";
    forward_not_perfect(x);      // 左值 -> 左值
    forward_not_perfect(100);    // 右值 -> 左值
    
    std::cout << "\n完美转发:\n";
    forward_perfect(x);          // 左值 -> 左值
    forward_perfect(100);        // 右值 -> 右值
    forward_perfect(std::move(x)); // 将亡值 -> 右值
    
    return 0;
}

完美转发在实现通用包装器、工厂函数等场景中非常有用:

#include <memory>
#include <iostream>

class Widget {
public:
    Widget() { std::cout << "Default constructor\n"; }
    Widget(int a, double b) { std::cout << "Constructor with args: " << a << ", " << b << "\n"; }
};

// 通用工厂函数
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

int main() {
    // 使用完美转发的工厂函数
    auto w1 = make_unique<Widget>();
    auto w2 = make_unique<Widget>(42, 3.14);
    
    return 0;
}

移动语义和完美转发是现代C++高效资源管理的基石,它们使得我们可以编写既安全又高效的代码。

需要传递或返回对象
对象是否昂贵拷贝?
使用值语义或引用
对象资源可否转移?
使用共享所有权
shared_ptr
实现移动语义
添加移动构造函数
添加移动赋值运算符
在适当场合使用std::move
需要模板转发参数?
使用完美转发
T&& + std::forward
完成

【举例】
移动语义就像搬家时的"盒子标记"。与其把每个物品重新买一份(拷贝),不如直接把标记好的盒子搬到新家(移动)。完美转发则像是一个专业的搬家团队,他们会根据物品的特性(值类别)决定最合适的搬运方式,保持一切原样。

7 模板元编程与概念:类型安全的泛型编程

7.1 模板基础:从函数模板到类模板

模板是C++泛型编程的基础,允许我们编写与类型无关的代码:

#include <iostream>
#include <vector>

// 函数模板
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 类模板
template<typename T>
class Container {
public:
    void add(const T& item) {
        items.push_back(item);
    }
    
    void print() const {
        for (const auto& item : items) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
    
private:
    std::vector<T> items;
};

// 非类型模板参数
template<typename T, int Size>
class FixedArray {
public:
    T& operator[](int index) {
        return data[index];
    }
    
    const T& operator[](int index) const {
        return data[index];
    }
    
private:
    T data[Size];
};

int main() {
    // 使用函数模板
    std::cout << max(3, 5) << std::endl;        // 5
    std::cout << max(3.14, 2.71) << std::endl;  // 3.14
    
    // 使用类模板
    Container<int> int_container;
    int_container.add(1);
    int_container.add(2);
    int_container.print();
    
    Container<std::string> str_container;
    str_container.add("Hello");
    str_container.add("World");
    str_container.print();
    
    // 使用非类型模板参数
    FixedArray<double, 10> array;
    array[0] = 3.14;
    
    return 0;
}

7.2 变参模板:处理任意数量和类型的参数

C++11引入了变参模板,可以处理任意数量和类型的参数:

#include <iostream>

// 基础 case:处理0个参数
void print() {
    std::cout << std::endl;
}

// 递归 case:处理1个或多个参数
template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...); // 递归调用
}

// 使用折叠表达式(C++17)
template<typename... Args>
void print_fold(Args... args) {
    (std::cout << ... << args) << std::endl; // 折叠表达式
}

// 变参模板应用:实现tuple
template<typename... Types>
class Tuple;

// 基本 case:空tuple
template<>
class Tuple<> {};

// 递归 case:非空tuple
template<typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
public:
    Tuple(Head head, Tail... tail) 
        : Tuple<Tail...>(tail...), head_(head) {}
    
    Head head() const { return head_; }
    Tuple<Tail...>& tail() { return *this; }
    
private:
    Head head_;
};

int main() {
    // 使用变参函数模板
    print(1, 2.5, "Hello", 'a'); // 1 2.5 Hello a
    print_fold(1, 2.5, "Hello", 'a'); // 12.5Helloa
    
    // 使用变参类模板
    Tuple<int, double, std::string> tuple(42, 3.14, "Hello");
    std::cout << tuple.head() << std::endl; // 42
    std::cout << tuple.tail().head() << std::endl; // 3.14
    
    return 0;
}

7.3 概念(Concepts):约束模板参数

C++20引入了概念,为模板参数添加约束,使模板编程更加安全和直观:

#include <iostream>
#include <concepts>
#include <vector>

// 定义概念
template<typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;

template<typename T>
concept HasSize = requires(T t) {
    { t.size() } -> std::convertible_to<std::size_t>;
};

// 使用概念约束函数模板
template<Arithmetic T>
T square(T x) {
    return x * x;
}

template<HasSize Container>
void print_size(const Container& container) {
    std::cout << "Size: " << container.size() << std::endl;
}

// 使用requires子句更复杂的约束
template<typename T>
requires requires(T t) { 
    { t.begin() } -> std::input_iterator;
    { t.end() } -> std::input_iterator;
}
void print_elements(const T& container) {
    for (const auto& element : container) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
}

int main() {
    // 使用受约束的模板
    std::cout << square(5) << std::endl;     // 25
    std::cout << square(2.5) << std::endl;   // 6.25
    // std::cout << square("hello") << std::endl; // 错误:不满足Arithmetic概念
    
    std::vector<int> vec = {1, 2, 3};
    print_size(vec); // Size: 3
    print_elements(vec); // 1 2 3
    
    // int not_container = 42;
    // print_size(not_container); // 错误:不满足HasSize概念
    
    return 0;
}

概念大大改善了模板错误信息,使泛型编程更加直观和安全。

编写模板代码
需要参数约束?
使用传统模板
选择约束方式
使用标准概念
std::integral等
定义自定义概念
使用requires子句
模板参数约束
编译时检查
通过: 实例化模板
不通过: 清晰错误信息

【举例】
概念就像是招聘要求。传统模板像是"招聘一个能做任何工作的人",而带概念的模板像是"招聘一个有计算机学位和3年经验的Java程序员"。后者更加明确,减少了匹配错误,也让"应聘者"(模板参数)知道是否适合这个"职位"(模板实例化)。

8 并发编程:多核时代的利器

8.1 线程管理:std::thread和std::jthread

C++11引入了标准线程库,使得编写跨平台多线程程序变得更加容易:

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>

void thread_function(int id) {
    std::cout << "Thread " << id << " started\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Thread " << id << " finished\n";
}

int main() {
    std::cout << "Main thread started\n";
    
    // 创建普通线程
    std::thread t1(thread_function, 1);
    
    // 使用lambda表达式
    std::thread t2([]() {
        std::cout << "Lambda thread started\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        std::cout << "Lambda thread finished\n";
    });
    
    // 等待线程完成
    t1.join();
    t2.join();
    
    // 创建线程数组
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(thread_function, i + 2);
    }
    
    // 等待所有线程
    for (auto& t : threads) {
        t.join();
    }
    
    // C++20引入的jthread(joining thread)
    // 析构时自动join,更安全
    std::jthread jt(thread_function, 99);
    
    std::cout << "Main thread finished\n";
    return 0;
}

8.2 同步原语:互斥锁和条件变量

多线程编程需要同步机制来避免数据竞争和确保正确执行顺序:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

class ThreadSafeQueue {
public:
    void push(int value) {
        std::unique_lock lock(mutex_);
        queue_.push(value);
        cond_.notify_one(); // 通知一个等待线程
    }
    
    int pop() {
        std::unique_lock lock(mutex_);
        // 等待直到队列非空
        cond_.wait(lock, [this]() { return !queue_.empty(); });
        
        int value = queue_.front();
        queue_.pop();
        return value;
    }
    
private:
    std::queue<int> queue_;
    std::mutex mutex_;
    std::condition_variable cond_;
};

int main() {
    ThreadSafeQueue queue;
    
    // 生产者线程
    std::thread producer([&queue]() {
        for (int i = 0; i < 10; ++i) {
            std::cout << "Producing: " << i << std::endl;
            queue.push(i);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    });
    
    // 消费者线程
    std::thread consumer([&queue]() {
        for (int i = 0; i < 10; ++i) {
            int value = queue.pop();
            std::cout << "Consuming: " << value << std::endl;
        }
    });
    
    producer.join();
    consumer.join();
    
    return 0;
}

8.3 异步编程:std::async和std::future

C++11提供了更高级的异步编程抽象:

#include <iostream>
#include <future>
#include <vector>
#include <numeric>
#include <chrono>

// 计算向量部分和的函数
int partial_sum(const std::vector<int>& data, int start, int end) {
    return std::accumulate(data.begin() + start, data.begin() + end, 0);
}

int main() {
    std::vector<int> data(1000);
    std::iota(data.begin(), data.end(), 1); // 填充1-1000
    
    // 使用async异步计算
    auto future1 = std::async(std::launch::async, partial_sum, std::ref(data), 0, 500);
    auto future2 = std::async(std::launch::async, partial_sum, std::ref(data), 500, 1000);
    
    // 可以同时做其他工作
    std::cout << "Doing other work...\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // 获取结果
    int sum1 = future1.get();
    int sum2 = future2.get();
    int total = sum1 + sum2;
    
    std::cout << "Sum of first half: " << sum1 << std::endl;
    std::cout << "Sum of second half: " << sum2 << std::endl;
    std::cout << "Total sum: " << total << std::endl;
    
    // 使用packaged_task和promise
    std::packaged_task<int()> task([]() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return 42;
    });
    
    std::future<int> result = task.get_future();
    std::thread task_thread(std::move(task));
    
    std::cout << "Waiting for result...\n";
    std::cout << "The answer is: " << result.get() << std::endl;
    
    task_thread.join();
    
    return 0;
}

现代C++的并发编程模型提供了从低级线程管理到高级异步操作的完整工具集。

需要并发执行任务
任务间需要共享数据?
使用std::async
异步执行
需要线程间通信?
使用互斥锁保护共享数据
使用条件变量或原子操作
选择锁类型
简单互斥: std::mutex
读写锁: std::shared_mutex
递归锁: std::recursive_mutex
通知等待: condition_variable
无锁编程: atomic
实现线程安全

【举例】
并发编程就像厨房里的多位厨师一起准备大餐。std::thread像是雇佣厨师,std::mutex像是厨房里某些厨具只能一个人使用的规则,condition_variable像是厨师之间的喊话"汤做好了!",而std::async像是点外卖——你不在乎谁做、在哪做,只关心结果。

9 范围库和协程:现代C++的新前沿

9.1 范围视图:声明式数据变换

C++20的范围库提供了声明式的数据操作方式,使代码更加简洁和表达力强:

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 传统的命令式方式
    std::vector<int> even_squares;
    for (int n : numbers) {
        if (n % 2 == 0) {
            even_squares.push_back(n * n);
        }
    }
    
    // 使用范围视图的声明式方式
    auto result = numbers 
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; });
    
    std::cout << "Even squares: ";
    for (int n : result) {
        std::cout << n << " "; // 4 16 36 64 100
    }
    std::cout << std::endl;
    
    // 更多范围操作示例
    std::vector<std::string> words = {"Hello", "World", "C++", "Ranges", "Example"};
    
    // 过滤、转换、取前N个
    auto processed = words
        | std::views::filter([](const std::string& s) { return s.size() > 3; })
        | std::views::transform([](const std::string& s) { 
            std::string result;
            for (char c : s) {
                result += std::toupper(c);
            }
            return result;
        })
        | std::views::take(2);
    
    std::cout << "Processed words: ";
    for (const auto& word : processed) {
        std::cout << word << " "; // HELLO WORLD
    }
    std::cout << std::endl;
    
    return 0;
}

9.2 协程:可暂停和恢复的函数

C++20引入了协程,这是一种可以暂停执行并在之后恢复的函数,非常适合异步编程:

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>

// 简单的协程返回类型
struct Generator {
    struct promise_type {
        int current_value;
        
        Generator get_return_object() {
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
        
        std::suspend_always yield_value(int value) {
            current_value = value;
            return {};
        }
        
        void return_void() {}
    };
    
    std::coroutine_handle<promise_type> coro;
    
    explicit Generator(std::coroutine_handle<promise_type> h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }
    
    int value() { return coro.promise().current_value; }
    void next() { coro.resume(); }
    bool done() { return coro.done(); }
};

// 协程函数
Generator generate_numbers(int start, int end) {
    for (int i = start; i <= end; ++i) {
        co_yield i; // 暂停并返回值
        std::cout << "Resumed after yielding " << i << std::endl;
    }
    co_return; // 结束协程
}

// 异步任务协程
struct AsyncTask {
    struct promise_type {
        std::string result;
        
        AsyncTask get_return_object() {
            return AsyncTask{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
        
        void return_value(const std::string& value) {
            result = value;
        }
    };
    
    std::coroutine_handle<promise_type> coro;
    
    explicit AsyncTask(std::coroutine_handle<promise_type> h) : coro(h) {}
    ~AsyncTask() { if (coro) coro.destroy(); }
    
    std::string get_result() {
        coro.resume();
        return coro.promise().result;
    }
};

AsyncTask perform_async_work() {
    std::cout << "Starting async work..." << std::endl;
    co_await std::suspend_always{}; // 模拟异步等待
    
    // 模拟一些工作
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    std::cout << "Async work completed" << std::endl;
    co_return "Success";
}

int main() {
    // 使用生成器协程
    std::cout << "=== Generator Coroutine ===\n";
    auto gen = generate_numbers(1, 5);
    
    while (!gen.done()) {
        gen.next();
        std::cout << "Generated: " << gen.value() << std::endl;
    }
    
    // 使用异步任务协程
    std::cout << "\n=== Async Task Coroutine ===\n";
    auto task = perform_async_work();
    std::cout << "Task created, getting result..." << std::endl;
    
    std::string result = task.get_result();
    std::cout << "Result: " << result << std::endl;
    
    return 0;
}

范围库和协程代表了现代C++的发展方向,提供了更高级的抽象和更优雅的编程模式。

需要处理数据序列
需要惰性求值或组合操作?
使用传统循环或算法
使用范围视图
选择视图适配器
过滤: views::filter
变换: views::transform
切片: views::take/drop
其他: reverse, keys, values等
组合视图管道
遍历结果
需要异步或生成器
使用协程
选择协程类型
生成器 Generator
异步任务 AsyncTask
其他自定义类型
使用co_yield产生值
使用co_await暂停等待
实现特定语义

【举例】
范围视图就像是一条流水线,数据从一端进入,经过各种处理(过滤、转换、切片),最后从另一端出来。协程则像是可以随时暂停和继续的电影,你可以在关键时刻暂停,去做其他事情,然后回来继续观看,而电影会从暂停的地方继续播放。

10 实战应用:构建一个简单的HTTP服务器

现在让我们综合运用现代C++的特性,构建一个简单的HTTP服务器:

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <memory>
#include <asio.hpp>
#include <functional>

using asio::ip::tcp;

// 线程安全的日志类
class Logger {
public:
    static Logger& instance() {
        static Logger logger;
        return logger;
    }
    
    void log(const std::string& message) {
        std::lock_guard lock(mutex_);
        std::cout << "[" << std::this_thread::get_id() << "] " << message << std::endl;
    }
    
private:
    Logger() = default;
    std::mutex mutex_;
};

// HTTP请求类
class HttpRequest {
public:
    std::string method;
    std::string path;
    std::string version;
    std::unordered_map<std::string, std::string> headers;
    std::string body;
    
    static std::optional<HttpRequest> parse(const std::string& raw_request) {
        HttpRequest request;
        std::istringstream stream(raw_request);
        std::string line;
        
        // 解析请求行
        if (!std::getline(stream, line)) return std::nullopt;
        std::istringstream line_stream(line);
        line_stream >> request.method >> request.path >> request.version;
        
        // 解析头部
        while (std::getline(stream, line) && line != "\r") {
            auto colon_pos = line.find(':');
            if (colon_pos != std::string::npos) {
                std::string key = line.substr(0, colon_pos);
                std::string value = line.substr(colon_pos + 1);
                // 去除首尾空白字符
                value.erase(0, value.find_first_not_of(" \t\r\n"));
                value.erase(value.find_last_not_of(" \t\r\n") + 1);
                request.headers[key] = value;
            }
        }
        
        // 解析正文(如果有)
        if (request.headers.count("Content-Length") > 0) {
            int content_length = std::stoi(request.headers["Content-Length"]);
            request.body.resize(content_length);
            stream.read(request.body.data(), content_length);
        }
        
        return request;
    }
};

// HTTP响应类
class HttpResponse {
public:
    int status_code = 200;
    std::string status_text = "OK";
    std::unordered_map<std::string, std::string> headers;
    std::string body;
    
    std::string to_string() const {
        std::string response;
        response += "HTTP/1.1 " + std::to_string(status_code) + " " + status_text + "\r\n";
        
        for (const auto& [key, value] : headers) {
            response += key + ": " + value + "\r\n";
        }
        
        if (!body.empty()) {
            response += "Content-Length: " + std::to_string(body.size()) + "\r\n";
        }
        
        response += "\r\n" + body;
        return response;
    }
    
    static HttpResponse not_found() {
        HttpResponse response;
        response.status_code = 404;
        response.status_text = "Not Found";
        response.body = "<h1>404 Not Found</h1>";
        response.headers["Content-Type"] = "text/html";
        return response;
    }
    
    static HttpResponse ok(const std::string& content, const std::string& content_type = "text/html") {
        HttpResponse response;
        response.body = content;
        response.headers["Content-Type"] = content_type;
        return response;
    }
};

// 连接处理类
class Connection : public std::enable_shared_from_this<Connection> {
public:
    Connection(tcp::socket socket) : socket_(std::move(socket)) {}
    
    void start() {
        read_request();
    }
    
private:
    void read_request() {
        auto self = shared_from_this();
        
        asio::async_read_until(socket_, buffer_, "\r\n\r\n",
            [this, self](std::error_code ec, std::size_t length) {
                if (!ec) {
                    std::string request_data(asio::buffers_begin(buffer_.data()),
                                           asio::buffers_begin(buffer_.data()) + length);
                    buffer_.consume(length);
                    
                    if (auto request = HttpRequest::parse(request_data)) {
                        handle_request(*request);
                    } else {
                        send_response(HttpResponse::not_found());
                    }
                } else {
                    Logger::instance().log("Read error: " + ec.message());
                }
            });
    }
    
    void handle_request(const HttpRequest& request) {
        Logger::instance().log("Request: " + request.method + " " + request.path);
        
        HttpResponse response;
        
        if (request.path == "/") {
            response = HttpResponse::ok("<h1>Hello, Modern C++!</h1>");
        } else if (request.path == "/api/data") {
            response = HttpResponse::ok(R"({"message": "Hello JSON"})", "application/json");
        } else {
            response = HttpResponse::not_found();
        }
        
        send_response(response);
    }
    
    void send_response(const HttpResponse& response) {
        auto self = shared_from_this();
        std::string response_str = response.to_string();
        
        asio::async_write(socket_, asio::buffer(response_str),
            [this, self](std::error_code ec, std::size_t) {
                if (!ec) {
                    // 保持连接活跃,准备处理下一个请求
                    read_request();
                } else {
                    Logger::instance().log("Write error: " + ec.message());
                }
            });
    }
    
    tcp::socket socket_;
    asio::streambuf buffer_;
};

// HTTP服务器类
class HttpServer {
public:
    HttpServer(asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        accept();
    }
    
private:
    void accept() {
        acceptor_.async_accept(
            [this](std::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<Connection>(std::move(socket))->start();
                } else {
                    Logger::instance().log("Accept error: " + ec.message());
                }
                accept(); // 继续接受新连接
            });
    }
    
    tcp::acceptor acceptor_;
};

int main() {
    try {
        asio::io_context io_context;
        HttpServer server(io_context, 8080);
        
        Logger::instance().log("Server started on port 8080");
        
        // 运行多个线程处理请求
        const int num_threads = 4;
        std::vector<std::thread> threads;
        
        for (int i = 0; i < num_threads; ++i) {
            threads.emplace_back([&io_context]() {
                io_context.run();
            });
        }
        
        for (auto& thread : threads) {
            thread.join();
        }
    } catch (std::exception& e) {
        Logger::instance().log("Exception: " + std::string(e.what()));
    }
    
    return 0;
}

这个HTTP服务器展示了现代C++多个特性的综合应用:

  1. 智能指针:使用shared_ptr管理连接生命周期
  2. Lambda表达式:用于异步回调
  3. 移动语义:高效转移socket所有权
  4. 并发编程:多线程处理请求
  5. 异步I/O:使用ASIO库进行异步操作
  6. 自动类型推导:使用auto简化代码
  7. 结构化绑定:处理键值对

这个服务器虽然简单,但包含了现代C++开发的许多核心概念和最佳实践。

结语

通过这趟旅程,我们探索了现代C++的强大特性和丰富功能。从自动类型推导到智能指针,从Lambda表达式到移动语义,从模板元编程到并发编程,再到最新的范围库和协程,现代C++已经发展成为一门既强大又优雅的语言。

现代C++的魅力在于它能够在保持高性能和底层控制的同时,提供高层次的抽象和表达能力。它既适合系统编程、游戏开发、高性能计算等传统领域,也适合Web服务、机器学习、物联网等现代应用场景。

最重要的是,现代C++仍在不断进化。C++23已经蓄势待发,将会带来更多令人兴奋的特性。作为C++开发者,我们正处在这门语言历史上最令人兴奋的时代之一。

无论你是C++新手还是经验丰富的老手,现代C++都有无穷无尽的知识等待你去探索。保持好奇心,持续学习,勇于实践,你一定会发现C++编程的乐趣和魅力。

记住,最好的学习方式就是动手实践。不要害怕犯错,不要畏惧挑战,在不断的编码和调试中,你会逐渐掌握现代C++的精髓,成为一名真正的C++大师。

Happy coding!

感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【Air】

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值