C++手撕共享指针、多线程交替、LRU缓存

发布于:2025-03-26 ⋅ 阅读:(32) ⋅ 点赞:(0)

 1. 共享指针

#include <atomic>
#include <iostream>

template <typename T> class sharedptr {
private:
  T *ptr;
  std::atomic<size_t> *count;

public:
  sharedptr(T *p) : ptr(p), count(new std::atomic<size_t>(1)) {}
  sharedptr(const sharedptr &other) : ptr(other.ptr), count(other.count) {
    (*count)++;
  }
  sharedptr(sharedptr &&other) : ptr(other.ptr), count(other.count) {
    other.ptr = nullptr;
    other.count = nullptr;
  }
  // 拷贝赋值运算符
  // 何时调用?当用一个 ​已存在的对象(左值)​
  // 赋值给另一个已存在的对象时: Myclass a,b;
  sharedptr &operator=(const sharedptr &other) {
    if (this != &other) {
      release();
      ptr = other.ptr;
      count = other.count;
      (*count)++;
    }
    return *this;
  }
  // 移动赋值运算符
  sharedptr &operator=(sharedptr &&other) {
    if (this != &other) {
      ptr = other.ptr;
      count = other.count;
      other.ptr = nullptr;
      other.count = nullptr;
    }
    return *this;
  }
  void release() {
    if (count != nullptr && (--(*count)) == 0) {
      delete ptr;
      delete count;
    }
  }
  ~sharedptr() { release(); }
  T *operator->() { return ptr; }
  T &operator*() { return *ptr; }
};
int main() {}

1. 快速记忆

  1. 双成员结构

    • T* ptr管理资源
    • atomic<size_t>* count保证线程安全
  2. 五大特殊函数

    // 1. 构造
    explicit Shared_Ptr(T* p = nullptr)
    
    // 2. 拷贝构造
    Shared_Ptr(const Shared_Ptr&)
    
    // 3. 移动构造(noexcept优化)
    Shared_Ptr(Shared_Ptr&&) noexcept
    
    // 4. 拷贝赋值(自检+释放旧值:等号左)
    operator=(const Shared_Ptr&)
    
    // 5. 移动赋值(自检+释放旧值:等号右)
    operator=(Shared_Ptr&&) noexcept
  3. 建议手写时按以下顺序实现

    • 成员变量

    • 构造函数
    • 拷贝构造/赋值
    • 移动构造/赋值
    • 析构函数
    • 操作符重载
    • 辅助方法

2. 多线程交替打印

代码:

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

int m;
std::mutex mtx;
std::condition_variable cv;
void print(int target) {
  for (int i = 0; i < 10; i++) {
    // 这行代码相当重要
    std::unique_lock<std::mutex> lock(mtx);
    // 条件谓词不接受参数
    cv.wait(lock, [&]() { return m == target; });
    std::cout << m << std::endl;
    m = (m + 1) % 3;
    cv.notify_all();
  }
}
int main() {
  m = 0;
  std::thread t1(print, 0);
  std::thread t2(print, 1);
  std::thread t3(print, 2);

  t1.join();
  t2.join();
  t3.join();
}

1. std::unique_lock<std::mutex>对象lock和std::mutex对象mtx是什么关系?

std::unique_lock<std::mutex> 是一个管理互斥量(std::mutex)的 RAII(资源获取即初始化)包装器。两者的关系如下:

  • 绑定关系:当你创建一个 std::unique_lock<std::mutex> 对象时,通常会将一个 std::mutex 对象(比如 mtx)传递给它。此时,unique_lock 会自动尝试锁定这个互斥量。

  • 自动管理unique_lock 在其生命周期内拥有并管理这个 mtx 的锁定状态。当 unique_lock 对象销毁时(例如超出作用域),它会自动解锁 mtx,避免手动解锁带来的错误风险。

  • 灵活性:与 std::lock_guard 相比,unique_lock 提供了更多的灵活性,比如能够延迟锁定、手动解锁或重新锁定,以及支持条件变量等待时的锁管理。

  • 个人理解:所谓mutex(mutual exclusion),本身仅是声明了一个互斥量,此时并不包含锁的意义

2. 简述一下cv.wait和notify做了什么?

  • cv.wait

    • 等待与解锁:调用 cv.wait(lock, predicate) 时,线程会进入等待状态,同时自动释放传入的 std::unique_lock 持有的互斥量。这使得其他线程可以获得该互斥量,从而修改共享数据。

    • 阻塞与恢复:当等待的条件(通常由传入的 lambda 表达式 predicate 检查)不满足时,线程会一直阻塞。待其他线程调用 notify 方法后,等待线程会被唤醒并重新尝试获取锁,随后检查条件,直到条件满足为止。

  • notify

    • 唤醒等待线程cv.notify_one() 会唤醒一个正等待此条件变量的线程,而 cv.notify_all() 则会唤醒所有等待的线程。

    • 同步机制:当某个线程修改了共享数据并满足了等待线程所期望的条件后,它会调用 notify 方法通知等待线程,促使它们从阻塞状态中恢复,继续执行。

3. LRU缓存

class LRUCache {
private:
    int capacity;
    list<pair<int,int>> mylist;
    unordered_map<int,list<pair<int,int>>::iterator> umap;
public:

    LRUCache(int capacity):capacity(capacity) {

    }
    
    int get(int key) {
        if(umap.find(key)==umap.end())  {return -1;}
        int value=umap[key]->second;
        mylist.erase(umap[key]);
        mylist.push_front({key,value});
        umap[key]=mylist.begin();
        return value;
    }
    
    void put(int key, int value) {
        if(umap.find(key)!=umap.end()){
            mylist.erase(umap[key]);
            mylist.push_front({key,value});
            umap[key]=mylist.begin();
        }   else{
                if(umap.size()==capacity){
                    //  不能直接删除end()
                    // umap.erase(mylist.end());
                    int old_key=mylist.back().first;
                    umap.erase(old_key);
                    mylist.pop_back();
                }
                mylist.push_front({key,value});
                umap[key]=mylist.begin();
        }
    }
};