C++学习 day15

发布于 13 天前  52 次阅读


Day 15:智能指针与多线程基础

本节代码位置:

2-代码/day15-智能指针/81-练习.cpp
2-代码/day15-智能指针/82-unique_ptr示例.cpp
2-代码/day15-智能指针/83-shared_ptr示例.cpp
2-代码/day15-智能指针/84-weak_ptr示例.cpp
2-代码/day15-智能指针/85-auto_ptr示例.cpp
2-代码/day15-智能指针/86-线程示例.cpp
2-代码/day15-智能指针/87-mutex使用示例.cpp
2-代码/day15-智能指针/88-条件变量示例.cpp

1、智能指针

智能指针位于 <memory>,用 RAII 管理动态资源:智能指针对象离开作用域时,会自动释放它拥有的资源。

unique_ptr:独占所有权。
shared_ptr:共享所有权,使用引用计数。
weak_ptr:弱引用,不增加 shared_ptr 的引用计数。

实际编程中优先使用 make_uniquemake_shared 创建对象。

2、unique_ptr 独占所有权

unique_ptr 同一时刻只能有一个拥有者,不能直接拷贝,但可以移动所有权:

auto p1 = std::make_unique<int>(200);

// auto p2 = p1; // 错误,不能拷贝
auto p2 = std::move(p1); // 所有权转移,p1 变为空

管理数组时需要使用数组形式:

auto p = std::make_unique<int[]>(5);
p[1] = 300;

unique_ptr 适用于资源只有一个明确拥有者的场景,例如类的私有资源和工厂函数返回对象。

3、shared_ptr 共享所有权

shared_ptr 通过引用计数管理对象。复制一个 shared_ptr,计数加一;最后一个拥有者销毁或重置后,对象才会释放。

auto p1 = std::make_shared<Demo>(400);
cout << p1.use_count() << endl; // 1

auto p2 = p1;
cout << p1.use_count() << endl; // 2

常用接口:

p.reset(new Demo(200)); // 改为管理新对象
Demo *raw = p.get();    // 取得裸指针,但不转移所有权

通过 get() 得到的裸指针不能单独 delete,因为对象仍由 shared_ptr 管理。

4、weak_ptr 弱引用

weak_ptr 用于观察 shared_ptr 管理的对象,但本身不拥有对象,因此不会增加引用计数。

std::weak_ptr<int> gw;

auto sp = std::make_shared<int>(42);
gw = sp;

对象可能已经被释放,使用前应先检查并通过 lock() 获取临时的 shared_ptr

if (!gw.expired())
{
    auto sp = gw.lock();
    cout << *sp << endl;
}
expired():判断对象是否已经释放。
lock():对象仍存在时返回 shared_ptr;对象不存在时返回空 shared_ptr。

weak_ptr 的典型用途是打破两个 shared_ptr 相互持有产生的循环引用。

5、auto_ptr 已废弃

auto_ptr<int> p1(new int(100));
auto p2 = p1; // 资源转移给 p2,p1 变为空

auto_ptr 的拷贝会悄悄转移所有权,容易产生意外的空指针错误。它在 C++11 已弃用,C++17 已移除;现代 C++ 应使用 unique_ptr

6、thread 线程

void task(int n)
{
    while (n--)
    {
        // 执行任务
    }
}

std::thread t(task, 5);
t.join();
join():等待线程结束。
detach():线程独立在后台运行,之后不能再 join。
线程对象销毁前必须 join 或 detach,否则会调用 std::terminate。

线程函数参数默认复制。要传引用参数,必须使用 std::ref

int n = 5;
std::thread t(task3, std::ref(n));
t.join();

成员函数作为线程入口时,需要同时传入成员函数地址和对象地址:

Demo d;
std::thread t(&Demo::task4, &d, std::ref(n));

7、mutex 互斥锁

多个线程同时读写共享数据会发生数据竞争。互斥锁保证同一时刻只有一个线程进入临界区:

std::mutex m;
int g = 0;

void task()
{
    for (int i = 0; i < 10000; ++i)
    {
        std::lock_guard<std::mutex> lock(m);
        ++g;
    }
}

lock_guard 使用 RAII:构造时加锁,离开作用域自动解锁,适合简单的临界区。

8、condition_variable 条件变量

条件变量用于线程之间等待某个状态成立,避免忙等。

std::unique_lock lock(m);
cv.wait(lock, []
{
    return processed;
});

修改共享状态后,用 notify_one()notify_all() 唤醒等待线程:

{
    std::lock_guard lock(m);
    ready = true;
}
cv.notify_one();

wait(lock, predicate) 会在条件不满足时释放锁并等待通知;被唤醒后重新获得锁并再次检查条件,因此也能处理虚假唤醒。

9、Day15 总结

1. 智能指针用 RAII 自动管理动态资源。
2. unique_ptr 独占所有权,只能移动,不能拷贝。
3. shared_ptr 用引用计数共享所有权;get() 不转移所有权。
4. weak_ptr 不增加引用计数,使用时通过 lock() 获取临时 shared_ptr。
5. auto_ptr 已被移除,使用 unique_ptr 替代。
6. thread 用于并发执行,销毁前必须 join 或 detach。
7. std::ref 用于向线程函数传递引用参数。
8. mutex 保护共享数据;lock_guard 自动管理锁。
9. condition_variable 用于等待和通知线程间的状态变化。

一句话记忆:智能指针管资源生命周期,mutex 管共享数据访问,条件变量负责线程间等待与唤醒。


"When faced with uncertainty, ask the spring breeze."