【后端】【C++】从裸指针到 C++20 协程:现代 C++ 内存与并发编程的双重革命

📖目录

  • [前言:VC++6.0 的幽灵还在游荡](#前言:VC++6.0 的幽灵还在游荡)
  • [1. 内存安全革命 ------ 智能指针如何终结 `new/delete` 地狱](#1. 内存安全革命 —— 智能指针如何终结 new/delete 地狱)
    • [1.1 裸指针的三大原罪](#1.1 裸指针的三大原罪)
    • [1.2 `unique_ptr`:独占所有权(像租用共享单车)](#1.2 unique_ptr:独占所有权(像租用共享单车))
    • [1.3 `shared_ptr` + `weak_ptr`:共享与防循环引用(像多人合租快递柜)](#1.3 shared_ptr + weak_ptr:共享与防循环引用(像多人合租快递柜))
  • [2. 并发安全革命 ------ 从裸线程到 C++20 协程](#2. 并发安全革命 —— 从裸线程到 C++20 协程)
    • [2.1 裸线程 vs `std::jthread`(自动 join 的快递员)](#2.1 裸线程 vs std::jthread(自动 join 的快递员))
    • [2.2 原子操作:`std::atomic` 与内存序](#2.2 原子操作:std::atomic 与内存序)
    • [2.3 同步原语:`mutex` + `scoped_lock`](#2.3 同步原语:mutex + scoped_lock)
    • [2.4 异步任务:`std::async` + `future`](#2.4 异步任务:std::async + future)
  • [3. C++20 协程实战 ------ 异步即同步](#3. C++20 协程实战 —— 异步即同步)
    • [3.1 协程核心概念大白话](#3.1 协程核心概念大白话)
    • [3.2 让 co_await 跑起来:task<void> 初探(最简可运行)](#3.2 让 co_await 跑起来:task<void> 初探(最简可运行))
    • [3.3 真实挂起与恢复:基于事件循环的协程调度器(完整可运行)](#3.3 真实挂起与恢复:基于事件循环的协程调度器(完整可运行))
  • [4. 性能对比与工程建议](#4. 性能对比与工程建议)
  • [5. 总结 & 下一篇预告](#5. 总结 & 下一篇预告)
  • [6. 参考资料 & 往期文章](#6. 参考资料 & 往期文章)

前言:VC++6.0 的幽灵还在游荡

在 2025 年,仍有大量 C++ 项目运行在 VC++6.0 上------没有模板偏特化、没有 STL 安全性、没有 RAII、没有多线程标准库。开发者每天与 new[] / delete[]_beginthreadexCRITICAL_SECTION 打交道,如同在雷区跳舞。

而现代 C++(C++11 ~ C++20)完成了两大革命:

  1. 内存安全:通过智能指针消灭野指针、内存泄漏;
  2. 并发安全:通过 RAII 线程、原子操作、协程消灭数据竞争、死锁、回调地狱。

本文将带你从零构建现代 C++ 编程范式 ,所有代码均可在 VS2022 / GCC10+ / Clang14+ 上编译运行(需启用 C++20)。


1. 内存安全革命 ------ 智能指针如何终结 new/delete 地狱

1.1 裸指针的三大原罪

cpp 复制代码
// VC++6.0 风格:危险三连
int* p = new int(42);
// 忘记 delete? → 内存泄漏
// 多次 delete? → 崩溃
// 异常抛出? → 永远不会 delete

💥 现实案例:某金融系统因未释放交易缓存,72 小时后 OOM 崩溃。

1.2 unique_ptr:独占所有权(像租用共享单车)

cpp 复制代码
// 文件:unique_ptr_demo.cpp
#include <memory>
#include <iostream>

class Package {
public:
    Package(int id) : id_(id) { std::cout << "📦 Package " << id_ << " created\n"; }
    ~Package() { std::cout << "🗑️ Package " << id_ << " destroyed\n"; }
    void deliver() { std::cout << "🚚 Delivering package " << id_ << "\n"; }
private:
    int id_;
};

int main() {
    // unique_ptr:唯一拥有者,离开作用域自动 delete
    std::unique_ptr<Package> pkg = std::make_unique<Package>(1001);
    pkg->deliver();

    // 无法复制,只能转移
    auto pkg2 = std::move(pkg); // pkg 变为空
    if (pkg == nullptr) {
        std::cout << "Original pointer is now null\n";
    }

    return 0;
    // pkg2 析构 → Package 自动销毁
}

输出

1.3 shared_ptr + weak_ptr:共享与防循环引用(像多人合租快递柜)

cpp 复制代码
// 文件:shared_weak_demo.cpp
#include <memory>
#include <iostream>

struct Node;
using NodePtr = std::shared_ptr<Node>;
using WeakNodePtr = std::weak_ptr<Node>;

struct Node {
    int value;
    NodePtr next;
    WeakNodePtr prev; // 用 weak_ptr 避免循环引用!

    Node(int v) : value(v) { std::cout << "Node " << v << " created\n"; }
    ~Node() { std::cout << "Node " << value << " destroyed\n"; }
};

int main() {
    auto n1 = std::make_shared<Node>(1);
    auto n2 = std::make_shared<Node>(2);

    n1->next = n2;
    n2->prev = n1; // weak_ptr,不增加引用计数

    std::cout << "n1 use_count: " << n1.use_count() << "\n"; // 1
    std::cout << "n2 use_count: " << n2.use_count() << "\n"; // 1

    return 0;
    // 无循环引用 → 两个节点正常析构
}

输出

❌ 若 prev 也是 shared_ptr,则 use_count 永远 ≥1,内存泄漏


2. 并发安全革命 ------ 从裸线程到 C++20 协程

2.1 裸线程 vs std::jthread(自动 join 的快递员)

cpp 复制代码
// 文件:jthread_vs_raw.cpp
#include <iostream>
#include <thread>
#include <chrono>

void worker(int id) {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    std::cout << "Worker " << id << " done\n";
}

int main() {
    // C++20 jthread:自动 join
    std::jthread t1(worker, 1);
    
    // 对比:std::thread 必须手动 join
    std::thread t2(worker, 2);
    t2.join(); // 忘记这句?程序 terminate!

    return 0;
}

输出

2.2 原子操作:std::atomic 与内存序

cpp 复制代码
// 文件:atomic_counter_full.cpp
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

std::atomic<int> counter{0};

void increment() {
    for (int i = 0; i < 10000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::vector<std::jthread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back(increment);
    }
    // 结果一定是 40000
    std::cout << "Final counter: " << counter.load() << "\n";
    return 0;
}

输出


2.3 同步原语:mutex + scoped_lock

cpp 复制代码
// 文件:mutex_logging.cpp
#include <mutex>
#include <iostream>
#include <thread>
#include <vector>

std::mutex cout_mutex;

void log_safe(int id) {
    std::scoped_lock lock(cout_mutex);
    std::cout << "Thread " << id << " writing log...\n";
}

int main() {
    std::vector<std::jthread> workers;
    for (int i = 0; i < 5; ++i) {
        workers.emplace_back(log_safe, i);
    }
    return 0;
}

输出

2.4 异步任务:std::async + future

cpp 复制代码
// 文件:async_future.cpp
#include <future>
#include <iostream>
#include <chrono>

int compute() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 123;
}

int main() {
    auto fut = std::async(std::launch::async, compute);
    std::cout << "Waiting for result...\n";
    int result = fut.get();
    std::cout << "Result: " << result << "\n";
    return 0;
}

输出


3. C++20 协程实战 ------ 异步即同步

3.1 协程核心概念大白话

  • co_await:请求挂起当前协程,直到被 await 的操作完成后再继续。是否真正挂起,取决于 awaitable 对象的实现。
  • 协程本身不会创建新线程------它们在调用线程上运行,通过协作式调度实现并发。

注意:下面的示例使用一个立即恢复的 awaiter,因此协程不会实际暂停。这仅用于演示基本语法。后续我们会展示真正的挂起与恢复。


3.2 让 co_await 跑起来:task 初探(最简可运行)

cpp 复制代码
// main.cpp
#include <iostream>
#include <coroutine>
#include <exception>
#include <stdexcept>   // <-- REQUIRED for std::runtime_error

struct task {
    struct promise_type {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

struct delay_awaiter {
    bool await_ready() const noexcept {
        return false;
    }

    void await_suspend(std::coroutine_handle<> handle) const noexcept {
        handle.resume();
    }

    void await_resume() const noexcept {}
};

delay_awaiter simulate_async_work() {
    std::cout << "Simulating async work...\n";
    return {};
}

task my_coroutine() {
    std::cout << "1. Start coroutine\n";
    co_await simulate_async_work();
    std::cout << "3. Coroutine resumed\n";
}

int main() {
    std::cout << "0. Before calling coroutine\n";
    my_coroutine();
    std::cout << "2. After calling coroutine\n";
    return 0;
}

输出

💡 关键点 :协程中可安全使用 unique_ptr/shared_ptr,RAII 依然生效!


3.3 真实挂起与恢复:基于事件循环的协程调度器(完整可运行)

💡 目标:展示协程如何在不创建新线程的情况下,通过 co_await 挂起,并在"未来某个时刻"被事件循环恢复执行。

cpp 复制代码
// 文件:coroutine_scheduler.cpp
// 编译命令(VS2022、VS2026):
// cl /std:c++20 /EHsc coroutine_scheduler.cpp
// GCC/Clang: g++ -std=c++20 -O2 coroutine_scheduler.cpp -pthread

#include <iostream>
#include <coroutine>
#include <vector>
#include <queue>
#include <chrono>
#include <thread>
#include <functional>
#include <optional>

// ----------------------------
// 1. 简单的时间点类型(毫秒)
// ----------------------------
using TimePoint = std::chrono::steady_clock::time_point;
using Duration = std::chrono::milliseconds;

// ----------------------------
// 2. 事件循环(单线程调度器)
// ----------------------------
class EventLoop {
public:
    // 注册一个在未来 time 点执行的回调
    void schedule(TimePoint time, std::function<void()> callback) {
        tasks_.push({time, std::move(callback)});
    }

    // 运行事件循环,直到所有任务完成
    void run() {
        while (!tasks_.empty()) {
            auto now = std::chrono::steady_clock::now();
            auto& [time, cb] = tasks_.top();

            if (now >= time) {
                cb();               // 执行协程恢复
                tasks_.pop();
            } else {
                std::this_thread::sleep_until(time);
            }
        }
    }

private:
    struct Task {
        TimePoint time;
        std::function<void()> callback;
        bool operator>(const Task& other) const {
            return time > other.time;
        }
    };
    std::priority_queue<Task, std::vector<Task>, std::greater<Task>> tasks_;
};

// 全局事件循环(简化设计)
EventLoop g_loop;

// ----------------------------
// 3. 可 await 的 delay 对象
// ----------------------------
struct DelayAwaiter {
    Duration delay_;

    explicit DelayAwaiter(Duration d) : delay_(d) {}

    bool await_ready() const noexcept {
        return false; // 总是挂起
    }

    void await_suspend(std::coroutine_handle<> handle) const {
        auto resume_time = std::chrono::steady_clock::now() + delay_;
        g_loop.schedule(resume_time, [handle]() {
            handle.resume(); // 在事件循环中恢复协程
        });
    }

    void await_resume() const noexcept {}
};

DelayAwaiter delay(Duration ms) {
    return DelayAwaiter{ms};
}

// ----------------------------
// 4. 支持返回值的 task<T>(简化版)
// ----------------------------
template<typename T>
struct task {
    struct promise_type {
        T value_;
        std::exception_ptr ex_;

        task get_return_object() { return task{std::coroutine_handle<promise_type>::from_promise(*this)}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }

        template<typename U>
        void return_value(U&& val) { value_ = std::forward<U>(val); }

        void unhandled_exception() { ex_ = std::current_exception(); }

        ~promise_type() = default;
    };

    using handle_type = std::coroutine_handle<promise_type>;
    handle_type coro_;

    explicit task(handle_type h) : coro_(h) {}
    task(const task&) = delete;
    task& operator=(const task&) = delete;

    task(task&& other) noexcept : coro_(other.coro_) { other.coro_ = nullptr; }

    ~task() {
        if (coro_) coro_.destroy();
    }

    T get() {
        coro_.resume(); // 触发 final_suspend 前的最后一次 resume
        if (coro_.promise().ex_)
            std::rethrow_exception(coro_.promise().ex_);
        return std::move(coro_.promise().value_);
    }
};

// ----------------------------
// 5. 协程函数示例
// ----------------------------
task<int> async_computation() {
    std::cout << "[Coroutine] Start async computation...\n";
    co_await delay(1000ms); // 挂起 1 秒
    std::cout << "[Coroutine] Resumed after delay!\n";
    co_return 42;
}

// ----------------------------
// 6. 主函数
// ----------------------------
int main() {
    std::cout << "0. Main starts\n";

    // 启动协程(不会立即执行完)
    task<int> t = async_computation();

    std::cout << "1. Coroutine launched, now running event loop...\n";

    // 运行事件循环,驱动协程恢复
    g_loop.run();

    std::cout << "2. Event loop done, getting result...\n";
    int result = t.get();
    std::cout << "3. Result = " << result << "\n";

    return 0;
}

✅ 预期输出(顺序严格):

🔍 关键说明:

  • 协程没有创建新线程:整个程序只在主线程运行。
  • delay(1000ms) 真正挂起协程 :通过 await_suspend 将恢复逻辑注册到事件循环。
  • 事件循环驱动恢复g_loop.run() 在 1 秒后调用 handle.resume(),协程继续执行。
  • 支持返回值task<int>co_return 并通过 .get() 获取结果。
  • 异常安全 :若协程内抛异常,会被捕获并通过 .get() 重新抛出。

🛠️ 编译提示(Visual Studio 2022):

  • 项目属性 → C/C++ → 语言 → C++ 语言标准:ISO C++20 标准 (/std:c++20)
  • 不需要额外链接库(纯标准库实现)

4. 性能对比与工程建议

技术 内存开销 线程数 适用场景
裸指针 + 裸线程 高 + 高 N/A 遗留系统维护
unique_ptr + jthread 低 + 中 CPU 密集型
shared_ptr + C++20 协程 极低 + 1 I/O 密集型(网络、DB)

📌 工程建议

  • 所有动态分配 → 用 make_unique / make_shared
  • 所有线程 → 用 jthread 或协程
  • 所有共享状态 → 用 atomicmutex + scoped_lock

5. 总结 & 下一篇预告

现代 C++ 的核心哲学是 "零成本抽象" + "资源即对象"

  • 智能指针 → 内存安全
  • RAII 线程 → 生命周期安全
  • 协程 → 异步逻辑清晰化

你不需要成为语言专家,只需遵循这些模式,就能写出安全、高效、可维护的工业级代码。

下一篇预告
《【后端】【C++】从 epoll 到 io_uring:Linux 高性能网络编程的终极演进》

我们将用 C++20 协程封装 Linux 最新 I/O 接口 io_uring,打造单机百万并发服务器!


6. 参考资料 & 往期文章


❤️ 如果你觉得本文有价值,请点赞、收藏、转发!

欢迎在评论区讨论:你们公司还在用 VC++6.0 吗? 😅


编译提示 :所有 .cpp 文件需用 C++20 标准 编译。

代码结构 :各 .cpp 示例,可直接复制运行。

如需我扩展 协程调度器实现无锁队列示例异常安全分析,请随时告诉我!

相关推荐
张np1 小时前
java基础-ArrayList
java·开发语言
Swizard1 小时前
别让 AI 假装在工作:Android "Vibe Coding" 的生存指南
android·java·vibe coding
BBB努力学习程序设计1 小时前
Java集合框架:管理数据的"超级工具箱"
java
库库林_沙琪马1 小时前
1、Hi~ SpringBoot
java·spring boot·后端
不会编程的小寒1 小时前
C / C++ 面试题
java·开发语言
BBB努力学习程序设计1 小时前
Java输入输出:让程序能与世界“对话”
java
电饭叔1 小时前
《python语言程序设计》2018版--第8章14题利用字符串输入作为一个信用卡号之一(Luhn算法解释)
android·java·python
阿宁又菜又爱玩1 小时前
Web后端开发入门
java·spring boot·后端·web
Z3r4y1 小时前
【代码审计】JeecgBoot-3.5.0 四处安全问题分析
java·web安全·代码审计·jeecg-boot