前言
校招 C++ 大概学习到什么程度
写明白下面这几个代码 +能讲明白几个 C++11/14/17 的特性
- MyString
- MyVector
- MyLRU
- MySingleton
- MyHashTable
- MySharedPtr
- MyUniquePtr
- MyWeakPtr
- MyThreadPool
- MyRingbuffer
- MyReadWriteMutex
- MyForward
- MyMove
MyString
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| class MyString { private: char* data; size_t len;
public: MyString() : data(nullptr), len(0) {}
MyString(const char* str) { len = std::strlen(str); data = new char[len + 1]; std::strcpy(data, str); }
MyString(const MyString& other) { len = other.len; data = new char[len + 1]; std::strcpy(data, other.data); }
MyString(MyString&& other) noexcept { data = other.data; len = other.len; other.data = nullptr; other.len = 0; }
MyString& operator=(MyString other) { swap(*this, other); return *this; }
MyString& operator=(MyString&& other) noexcept { if (this != &other) { delete[] data; data = other.data; len = other.len; other.data = nullptr; other.len = 0; } return *this; }
~MyString() { delete[] data; }
friend void swap(MyString& first, MyString& second) noexcept { using std::swap; swap(first.data, second.data); swap(first.len, second.len); }
const char* c_str() const { return data; } size_t length() const { return len; } };
|
这个实现展示了 C++ 中字符串类的基本实现,包括:
- 深拷贝构造函数:确保对象之间的独立性
- 移动构造函数和移动赋值:利用 C++11 特性提高性能
- 拷贝赋值运算符使用"拷贝交换"技术保证异常安全
- 析构函数正确释放资源
- 使用友元函数实现交换操作,
- 提供基本的字符串访问方法 c_str() 和 length()
MyVector
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| template <typename T> class MyVector { private: T* elements; size_t capacity; size_t size;
public: MyVector() : elements(nullptr), capacity(0), size(0) {}
explicit MyVector(size_t initialSize) : elements(new T[initialSize]), capacity(initialSize), size(initialSize) {}
MyVector(const MyVector& other) { capacity = other.capacity; size = other.size; elements = new T[capacity]; for (size_t i = 0; i < size; ++i) { elements[i] = other.elements[i]; } }
MyVector(MyVector&& other) noexcept { elements = other.elements; capacity = other.capacity; size = other.size; other.elements = nullptr; other.capacity = 0; other.size = 0; }
~MyVector() { delete[] elements; }
MyVector& operator=(const MyVector& other) { if (this != &other) { T* newElements = new T[other.capacity]; for (size_t i = 0; i < other.size; ++i) { newElements[i] = other.elements[i]; } delete[] elements; elements = newElements; capacity = other.capacity; size = other.size; } return *this; }
MyVector& operator=(MyVector&& other) noexcept { if (this != &other) { delete[] elements; elements = other.elements; capacity = other.capacity; size = other.size; other.elements = nullptr; other.capacity = 0; other.size = 0; } return *this; }
void push_back(const T& value) { if (size == capacity) { reserve(capacity == 0 ? 1 : capacity * 2); } elements[size++] = value; }
T& operator[](size_t index) { return elements[index]; }
const T& operator[](size_t index) const { return elements[index]; }
size_t get_size() const { return size; }
size_t get_capacity() const { return capacity; }
private: void reserve(size_t newCapacity) { if (newCapacity > capacity) { T* newElements = new T[newCapacity]; for (size_t i = 0; i < size; ++i) { newElements[i] = elements[i]; } delete[] elements; elements = newElements; capacity = newCapacity; } } };
|
这个自定义 vector 实现了标准库 vector 的核心功能:
- 动态扩容:当空间不足时自动将容量翻倍
- 深拷贝构造和赋值:确保对象独立性
- 移动语义:利用 C++11 特性提升性能
- 下标访问运算符:提供便捷的元素访问方式
- 基本的内存管理:包括构造、析构、复制和移动操作
- 常用操作如 push_back 和 reserve
该实现展示了对内存管理、资源所有权转移以及现代 C++ 特性的理解。
MyLRU
经典 LRU
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <iostream> #include <list> #include <unordered_map>
template <typename K, typename V> class LRUCache { private: size_t capacity; std::list<std::pair<K, V>> cache; std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> map;
public: LRUCache(size_t cap) : capacity(cap) {}
bool get(const K& key, V& value) { auto it = map.find(key); if (it == map.end()) return false; cache.splice(cache.begin(), cache, it->second); value = it->second->second; return true; }
void put(const K& key, const V& value) { if (auto it = map.find(key); it != map.end()) { cache.erase(it->second); } cache.push_front({key, value}); map[key] = cache.begin();
if (map.size() > capacity) { auto last = cache.back(); map.erase(last.first); cache.pop_back(); } }
void print() { for (auto& p : cache) { std::cout << "{" << p.first << ": " << p.second << "} "; } std::cout << std::endl; } };
|
带过期时间的 LRU(TTL-LRU)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include <iostream> #include <list> #include <unordered_map> #include <chrono>
template <typename K, typename V> class TTL_LRUCache { private: using TimePoint = std::chrono::steady_clock::time_point; struct CacheNode { K key; V value; TimePoint expiry; CacheNode(const K& k, const V& v, int ttl_sec) : key(k), value(v), expiry(std::chrono::steady_clock::now() + std::chrono::seconds(ttl_sec)) {} };
size_t capacity; std::list<CacheNode> cache; std::unordered_map<K, typename std::list<CacheNode>::iterator> map;
bool isExpired(const CacheNode& node) { return std::chrono::steady_clock::now() > node.expiry; }
public: TTL_LRUCache(size_t cap) : capacity(cap) {}
bool get(const K& key, V& value) { auto it = map.find(key); if (it == map.end()) return false; if (isExpired(*it->second)) { cache.erase(it->second); map.erase(it); return false; } cache.splice(cache.begin(), cache, it->second); value = it->second->value; return true; }
void put(const K& key, const V& value, int ttl_sec) { if (auto it = map.find(key); it != map.end()) { cache.erase(it->second); } cache.push_front(CacheNode(key, value, ttl_sec)); map[key] = cache.begin();
if (map.size() > capacity) { auto last = cache.back(); map.erase(last.key); cache.pop_back(); } }
void print() { for (auto& node : cache) { std::cout << "{" << node.key << ": " << node.value << "} "; } std::cout << std::endl; } };
|
LRU-K
MySingleton
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <mutex> #include <memory>
class Singleton { private: static Singleton* instance_; static std::mutex mutex_; Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete;
public: static Singleton* GetInstance() { std::lock_guard<std::mutex> lock(mutex_); if (instance_ == nullptr) { instance_ = new Singleton(); } return instance_; } static void DestroyInstance() { std::lock_guard<std::mutex> lock(mutex_); delete instance_; instance_ = nullptr; } void DoSomething() { std::cout << "Singleton instance is doing something." << std::endl; } };
Singleton* Singleton::instance_ = nullptr; std::mutex Singleton::mutex_;
|
这是一个线程安全的单例模式实现,包含以下关键点:
- 私有构造函数:防止外部创建多个实例
- 删除拷贝构造函数和赋值运算符:防止对象被复制
- 静态获取实例方法:提供全局访问点
- 懒汉式加载:在第一次调用 GetInstance() 时才创建实例
- 线程安全:通过 std::mutex 和 std::lock_guard 保证多线程环境下的安全性
- 手动内存管理:需要显式调用 DestroyInstance() 来释放内存
更高级的变体可能使用 std::call_once 或局部静态变量实现更简洁的线程安全版本:
1 2 3 4
| static Singleton& GetInstance() { static Singleton instance; return instance; }
|
MyHashTable
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| #include <vector> #include <list> #include <utility> #include <functional> #include <stdexcept>
template <typename KeyType, typename ValueType, typename Hash = std::hash<KeyType>> class HashTable { private: using KeyValuePair = std::pair<KeyType, ValueType>; using BucketType = std::list<KeyValuePair>; std::vector<BucketType> buckets_; size_t size_; Hash hash_function_; static constexpr double MAX_LOAD_FACTOR = 0.75;
size_t getBucketIndex(const KeyType& key) const { return hash_function_(key) % buckets_.size(); } void rehash() { std::vector<BucketType> oldBuckets = std::move(buckets_); buckets_.resize(oldBuckets.size() * 2); for (auto& bucket : oldBuckets) { for (auto& pair : bucket) { size_t newIndex = hash_function_(pair.first) % buckets_.size(); buckets_[newIndex].push_back(std::move(pair)); } } }
public: HashTable(size_t initialCapacity = 16) : buckets_(initialCapacity), size_(0) {} bool insert(const KeyType& key, const ValueType& value) { if (static_cast<double>(size_) / buckets_.size() >= MAX_LOAD_FACTOR) { rehash(); } size_t index = getBucketIndex(key); BucketType& bucket = buckets_[index]; for (auto& pair : bucket) { if (pair.first == key) { return false; } } bucket.emplace_back(key, value); ++size_; return true; } bool find(const KeyType& key, ValueType& outValue) const { size_t index = getBucketIndex(key); const BucketType& bucket = buckets_[index]; for (const auto& pair : bucket) { if (pair.first == key) { outValue = pair.second; return true; } } return false; } bool remove(const KeyType& key) { size_t index = getBucketIndex(key); BucketType& bucket = buckets_[index]; for (auto it = bucket.begin(); it != bucket.end(); ++it) { if (it->first == key) { bucket.erase(it); --size_; return true; } } return false; } bool update(const KeyType& key, const ValueType& newValue) { size_t index = getBucketIndex(key); BucketType& bucket = buckets_[index]; for (auto& pair : bucket) { if (pair.first == key) { pair.second = newValue; return true; } } return false; } size_t size() const { return size_; } bool empty() const { return size_ == 0; } };
|
这个哈希表实现展示了以下几个核心概念:
- 开放地址法 vs 分离链接法:这里使用的是分离链接法,每个桶是一个链表,用来处理哈希冲突
- 哈希函数:默认使用 std::hash,允许用户自定义哈希函数
- 负载因子与动态扩容:当负载因子超过阈值 (0.75) 时,进行 rehash 操作
- 时间复杂度:理想情况下,插入、查找、删除的时间复杂度都是 O(1)
- 多种操作支持:包括 insert、find、remove、update 等常用操作
实现特点:
- 使用 vector 存储桶,每个桶是链表 (list),可以有效处理哈希冲突
- 动态扩容机制保证了平均性能
- 支持自定义哈希函数,提高了灵活性
- 完整的错误处理和返回状态码
这个实现体现了对哈希表原理、冲突解决策略以及性能优化的深入理解。
MySharedPtr
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| #include <iostream> #include <atomic>
template <typename T> class SharedPtr { private: T* ptr_; std::atomic<int>* ref_count_;
public: SharedPtr() : ptr_(nullptr), ref_count_(new std::atomic<int>(0)) {}
explicit SharedPtr(T* ptr) : ptr_(ptr), ref_count_(new std::atomic<int>(1)) {}
SharedPtr(const SharedPtr<T>& other) : ptr_(other.ptr_), ref_count_(other.ref_count_) { if (ptr_) { (*ref_count_)++; } }
SharedPtr(SharedPtr<T>&&& other) noexcept : ptr_(other.ptr_), ref_count_(other.ref_count_) { other.ptr_ = nullptr; other.ref_count_ = nullptr; }
SharedPtr<T>& operator=(const SharedPtr<T>& other) { if (this != &other) { reset(); ptr_ = other.ptr_; ref_count_ = other.ref_count_; if (ptr_) { (*ref_count_)++; } } return *this; }
SharedPtr<T>& operator=(SharedPtr<T>&& other) noexcept { if (this != &other) { reset(); ptr_ = other.ptr_; ref_count_ = other.ref_count_; other.ptr_ = nullptr; other.ref_count_ = nullptr; } return *this; }
~SharedPtr() { reset(); }
void reset() { if (ref_count_ && --(*ref_count_) == 0) { delete ptr_; delete ref_count_; } ptr_ = nullptr; ref_count_ = nullptr; }
void reset(T* ptr) { reset(); ptr_ = ptr; ref_count_ = new std::atomic<int>(1); }
T* get() const { return ptr_; }
int use_count() const { return ref_count_ ? ref_count_->load() : 0; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; } };
|
这个 shared_ptr 实现展示了几个关键概念:
- 引用计数:使用原子变量 std::atomic来跟踪有多少个 shared_ptr 共享同一个对象
- 深拷贝 vs 浅拷贝:拷贝构造函数和赋值运算符增加引用计数而不是复制实际对象
- 移动语义:利用 C++11 的移动特性高效地转移所有权
- RAII 原则:在析构函数中自动释放资源
- 内存泄漏预防:确保在所有情况下都正确释放资源
- 安全布尔转换:避免隐式类型转换带来的问题
主要特性:
- 支持 get() 获取原始指针
- 支持 use_count() 查询引用计数
- 支持 reset() 手动释放资源
- 重载了解引用和成员访问操作符,使其行为像普通指针
- 提供了 bool 转换以检查有效性
这个实现体现了对智能指针设计原理、内存管理和并发控制的理解。
MyUniquePtr
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <iostream>
template <typename T> class UniquePtr { private: T* ptr_;
public: explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {}
UniquePtr(const UniquePtr<T>& other) = delete;
UniquePtr<T>& operator=(const UniquePtr<T>& other) = delete;
UniquePtr(UniquePtr<T>&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
template <typename U> UniquePtr<T>& operator=(UniquePtr<U>&& other) noexcept { if (static_cast<void*>(this) != reinterpret_cast<void*>(&other)) { delete ptr_; ptr_ = other.ptr_; other.ptr_ = nullptr; } return *this; }
~UniquePtr() { delete ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
T* release() { T* temp = ptr_; ptr_ = nullptr; return temp; }
void reset(T* other = nullptr) { if (ptr_ != other) { delete ptr_; ptr_ = other; } }
explicit operator bool() const { return ptr_ != nullptr; }
void swap(UniquePtr<T>& other) noexcept { std::swap(ptr_, other.ptr_); } };
|
这个 unique_ptr 实现展示了以下几个关键概念:
- 独占所有权:禁止拷贝构造和拷贝赋值,只能通过移动语义转移所有权
- RAII 原则:在析构函数中自动释放资源
- 移动语义:利用 C++11 的移动特性高效地转移所有权
- 安全布尔转换:避免隐式类型转换带来的问题
- 明确的资源转移机制:通过 release() 和 reset() 方法控制资源的所有权
主要特性:
- 支持 get() 获取原始指针
- 支持 release() 放弃所有权并返回原始指针
- 支持 reset() 替换托管对象
- 重载了解引用和成员访问操作符,使其行为像普通指针
- 提供了 swap 方法交换两个 unique_ptr 的内容
这个实现体现了对独占式资源管理的理解,以及如何通过删除拷贝操作确保单一所有权模型。
MyWeakPtr
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
| #include <iostream>
template <typename T> class WeakPtr;
template <typename T> class SharedPtr { private: T* ptr_; int* ref_count_; friend class WeakPtr<T>;
public: SharedPtr() : ptr_(nullptr), ref_count_(new int(0)) {}
explicit SharedPtr(T* ptr) : ptr_(ptr), ref_count_(new int(1)) {}
SharedPtr(const SharedPtr<T>& other) : ptr_(other.ptr_), ref_count_(other.ref_count_) { if (ptr_) { (*ref_count_)++; } }
SharedPtr(SharedPtr<T>&& other) noexcept { ptr_ = other.ptr_; ref_count_ = other.ref_count_; other.ptr_ = nullptr; other.ref_count_ = nullptr; }
SharedPtr<T>& operator=(const SharedPtr<T>& other) { if (this != &other) { reset(); ptr_ = other.ptr_; ref_count_ = other.ref_count_; if (ptr_) { (*ref_count_)++; } } return *this; }
SharedPtr<T>& operator=(SharedPtr<T>&& other) noexcept { if (this != &other) { reset(); ptr_ = other.ptr_; ref_count_ = other.ref_count_; other.ptr_ = nullptr; other.ref_count_ = nullptr; } return *this; }
~SharedPtr() { reset(); }
void reset() { if (ref_count_) { (*ref_count_)--; if (*ref_count_ == 0) { delete ptr_; delete ref_count_; } } ptr_ = nullptr; ref_count_ = nullptr; }
void reset(T* ptr) { reset(); ptr_ = ptr; ref_count_ = new int(1); }
T* get() const { return ptr_; }
int use_count() const { return ref_count_ ? *ref_count_ : 0; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; } };
template <typename T> class WeakPtr { private: T* ptr_; int* ref_count_;
public: WeakPtr() : ptr_(nullptr), ref_count_(nullptr) {}
WeakPtr(const SharedPtr<T>& sp) : ptr_(sp.ptr_), ref_count_(sp.ref_count_) { if (ptr_) { (*ref_count_)++; } }
WeakPtr(const WeakPtr<T>& other) : ptr_(other.ptr_), ref_count_(other.ref_count_) { if (ptr_) { (*ref_count_)++; } }
WeakPtr(WeakPtr<T>&& other) noexcept { ptr_ = other.ptr_; ref_count_ = other.ref_count_; other.ptr_ = nullptr; other.ref_count_ = nullptr; }
WeakPtr<T>& operator=(const WeakPtr<T>& other) { if (this != &other) { release(); ptr_ = other.ptr_; ref_count_ = other.ref_count_; if (ptr_) { (*ref_count_)++; } } return *this; }
WeakPtr<T>& operator=(WeakPtr<T>&& other) noexcept { if (this != &other) { release(); ptr_ = other.ptr_; ref_count_ = other.ref_count_; other.ptr_ = nullptr; other.ref_count_ = nullptr; } return *this; }
WeakPtr<T>& operator=(const SharedPtr<T>& sp) { release(); ptr_ = sp.ptr_; ref_count_ = sp.ref_count_; if (ptr_) { (*ref_count_)++; } return *this; }
~WeakPtr() { release(); }
void release() { if (ref_count_) { (*ref_count_)--; if (*ref_count_ == 0) { delete ptr_; delete ref_count_; } } ptr_ = nullptr; ref_count_ = nullptr; }
bool expired() const { return ref_count_ == nullptr || *ref_count_ == 0; }
SharedPtr<T> lock() const { if (expired()) { return SharedPtr<T>(); } return SharedPtr<T>(ptr_, ref_count_); }
void swap(WeakPtr<T>& other) { std::swap(ptr_, other.ptr_); std::swap(ref_count_, other.ref_count_); }
T* get() const { if (expired()) { return nullptr; } return ptr_; } };
|
这个 weak_ptr 实现展示了以下几个关键概念:
- 与 shared_ptr 配合使用:weak_ptr 不会增加强引用计数,而是通过观察 shared_ptr 管理的对象
- 循环引用解决方案:通过弱引用来打破循环引用,避免内存泄漏
- 引用计数的管理:除了强引用计数外,还需要维护弱引用计数
- 资源安全访问:通过 lock() 方法获取一个 shared_ptr 来确保对象的有效期
主要特性:
- 支持从 shared_ptr 构造
- 支持 expired() 检查对象是否已经被释放
- 支持 lock() 获取临时的 shared_ptr
- 正确处理引用计数的增减
- 实现了完整的拷贝、移动构造和赋值操作
这个实现体现了对资源生命周期管理、引用计数机制和循环引用问题的理解。
MyThreadPool
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| #include <iostream> #include <vector> #include <queue> #include <thread> #include <functional> #include <mutex> #include <condition_variable> #include <future> #include <atomic>
class ThreadPool { public: explicit ThreadPool(size_t numThreads) { start(numThreads); }
~ThreadPool() { stop(); }
template<typename T> std::future<typename std::result_of<T()>::type> submit(T task) { using result_type = typename std::result_of<T()>::type; auto packagedTask = std::make_shared<std::packaged_task<result_type()>>(std::move(task)); Task work = [packagedTask]() { (*packagedTask)(); }; { std::unique_lock<std::mutex> lock(queueMutex_); tasks_.push(std::move(work)); } condition_.notify_one(); return packagedTask->get_future(); }
private: void workerThread() { while (true) { Task task; { std::unique_lock<std::mutex> lock(queueMutex_); condition_.wait(lock, [this]() { return !running_ || !tasks_.empty(); }); if (!running_ && tasks_.empty()) { return; } task = std::move(tasks_.front()); tasks_.pop(); } task(); } }
void start(size_t numThreads) { running_ = true; workers_.reserve(numThreads); for (size_t i = 0; i < numThreads; ++i) { workers_.emplace_back(&ThreadPool::workerThread, this); } }
void stop() { { std::unique_lock<std::mutex> lock(queueMutex_); running_ = false; } condition_.notify_all(); for (std::thread& worker : workers_) { if (worker.joinable()) { worker.join(); } } }
using Task = std::function<void()>; std::vector<std::thread> workers_; std::queue<Task> tasks_; std::mutex queueMutex_; std::condition_variable condition_; std::atomic<bool> running_; };
|
这个线程池实现展示了以下几个关键概念:
- 线程管理:启动固定数量的工作线程并在析构时优雅关闭
- 任务队列:使用线程安全的队列存储待执行的任务
- 异步执行:通过 future 返回任务结果
- 条件同步:使用条件变量避免忙等待,提高效率
- 资源回收:确保所有工作线程在析构时正确结束
主要特性:
- 支持提交任意可调用对象作为任务
- 返回 std::future 以便获取任务结果
- 使用 std::packaged_task 包装任务,简化异步执行逻辑
- 支持优雅的启动和停止流程
- 线程安全的设计,使用互斥锁保护共享资源
这个实现体现了对多线程编程、并发控制和异步任务处理的理解。
MyRingbuffer
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| #include <iostream> #include <vector> #include <mutex> #include <condition_variable> #include <stdexcept>
template <typename T> class RingBuffer { public: explicit RingBuffer(size_t capacity) : buffer_(capacity), capacity_(capacity), head_(0), tail_(0), full_(false) {}
void write(const T& item) { std::lock_guard<std::mutex> lock(mutex_);
if (full_) { head_ = (head_ + 1) % capacity_; }
buffer_[tail_] = item; tail_ = (tail_ + 1) % capacity_;
full_ = (tail_ == head_);
not_empty_condition_.notify_one(); }
T read() { std::unique_lock<std::mutex> lock(mutex_);
not_empty_condition_.wait(lock, [this]() { return !empty(); });
T item = buffer_[head_]; head_ = (head_ + 1) % capacity_; full_ = false;
return item; }
bool try_read(T& item) { std::lock_guard<std::mutex> lock(mutex_);
if (empty()) { return false; }
item = buffer_[head_]; head_ = (head_ + 1) % capacity_; full_ = false;
return true; }
bool empty() const { return (!full_ && (head_ == tail_)); }
bool full() const { return full_; }
size_t size() const { if (full_) { return capacity_; } else if (head_ <= tail_) { return tail_ - head_; } else { return capacity_ - head_ + tail_; } }
size_t capacity() const { return capacity_; }
void reset() { std::lock_guard<std::mutex> lock(mutex_); head_ = 0; tail_ = 0; full_ = false; }
private: std::vector<T> buffer_; const size_t capacity_; size_t head_; size_t tail_; bool full_;
mutable std::mutex mutex_; std::condition_variable not_empty_condition_; };
|
这个环形缓冲区 (Ring Buffer) 实现展示了以下几个关键概念:
- 环形结构:使用数组实现的先进先出 (FIFO) 缓冲区
- 指针管理:通过 head 和 tail 指针追踪读写位置
- 线程安全:使用互斥锁和条件变量保证多线程环境下的安全性
- 数据覆盖策略:当缓冲区满时可以选择覆盖旧数据
- 阻塞与非阻塞操作:提供阻塞的 read() 和非阻塞的 try_read()
主要特性:
- 支持线程安全的读写操作
- 提供检查缓冲区状态的方法 (empty(), full(), size())
- 支持尝试读取和阻塞读取两种模式
- 可以重置缓冲区
- 使用条件变量实现生产者 - 消费者模式
这个实现体现了对并发编程、缓冲区管理和系统级同步原语的理解。
MyReadWriteMutex
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <atomic> #include <vector>
class ReadWriteMutex { public: ReadWriteMutex() : readers_count_(0), writers_waiting_(0), writer_active_(false) {}
void lock_read() { std::unique_lock<std::mutex> lock(mutex_); reader_condition_.wait(lock, [this]() { return !writer_active_; return !writer_active_ && writers_waiting_ == 0; }); readers_count_++; }
void unlock_read() { std::unique_lock<std::mutex> lock(mutex_); readers_count_--; if (readers_count_ == 0 && writers_waiting_ > 0) { writer_condition_.notify_one(); } }
void lock_write() { std::unique_lock<std::mutex> lock(mutex_); writers_waiting_++; writer_condition_.wait(lock, [this]() { return readers_count_ == 0 && !writer_active_; }); writers_waiting_--; writer_active_ = true; }
void unlock_write() { std::unique_lock<std::mutex> lock(mutex_); writer_active_ = false; if (writers_waiting_ > 0) { writer_condition_.notify_one(); } else { reader_condition_.notify_all(); } }
private: mutable std::mutex mutex_; std::condition_variable reader_condition_; std::condition_variable writer_condition_; unsigned int readers_count_; unsigned int writers_waiting_; bool writer_active_; };
|
这个读写锁实现展示了以下几个关键概念:
- 读者 - 写者问题:允许多个读者同时访问,但只允许一个写者访问
- 优先级策略:采用写者优先策略,一旦有写者等待,后续的读者必须等待
- 条件同步:使用条件变量等待适当的时机获得锁
- 状态管理:跟踪读者数量、写者等待状态和写者活动状态
主要特性:
- 支持读者加锁和解锁
- 支持写者加锁和解锁
- 保证写者的优先级高于读者
- 在适当的时候唤醒等待的线程(写者优先)
这个实现体现了对并发控制、条件同步和资源竞争问题的理解。
MyForward
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <iostream> #include <utility>
template <typename T> struct MyRemoveReference { using type = T; };
template <typename T> struct MyRemoveReference<T&> { using type = T; };
template <typename T> struct MyRemoveReference<T&&> { using type = T; };
template <typename T> using MyRemoveReference_t = typename MyRemoveReference<T>::type;
template <typename T> T&& my_forward(MyRemoveReference_t<T>& arg) noexcept { return static_cast<T&&>(arg); }
template <typename T> void wrapper(T&& arg) { call(my_forward<T>(arg)); }
void call(int& x) { std::cout << "lvalue reference called: " << x << std::endl; }
void call(int&& x) { std::cout << "rvalue reference called: " << x << std::endl; }
int main() { int a = 42; call(a); call(43); wrapper(a); wrapper(44); return 0; }
|
这个实现展示了 std::forward 的核心原理:
- 引用折叠规则:理解 T&&在不同情况下的含义
- 类型擦除:使用自定义的 MyRemoveReference 来去除引用属性
- 完美转发:保持参数的左值/右值属性传递给另一个函数
实现要点:
- MyRemoveReference 模板用于获取原始类型
- my_forward 函数使用 static_cast<T&&>进行强制转换
- 在模板函数 wrapper 中使用 my_forward 实现完美转发
这个例子还展示了完美转发的实际应用场景,通过测试函数验证了转发的正确性。
完美转发对于编写泛型代码非常重要,特别是在实现工厂函数、容器的 emplace 系列方法等场景下非常有用。
MyMove
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include <iostream> #include <type_traits>
template <class T> struct my_remove_reference { using type = T; };
template <class T> struct my_remove_reference<T&> { using type = T; };
template <class T> struct my_remove_reference<T&&> { using type = T; };
template <class T> using my_remove_reference_t = typename my_remove_reference<T>::type;
template <class T> my_remove_reference_t<T>&& my_move(T&& obj) noexcept { return static_cast<my_remove_reference_t<T>&&>(obj); }
class MyClass { public: MyClass() { std::cout << "Constructor\n"; } MyClass(const MyClass&) { std::cout << "Copy constructor\n"; } MyClass(MyClass&&) noexcept { std::cout << "Move constructor\n"; } MyClass& operator=(const MyClass&) { std::cout << "Copy assignment\n"; return *this; } MyClass& operator=(MyClass&&) noexcept { std::cout << "Move assignment\n"; return *this; } };
int main() { int x = 42; MyClass obj1; MyClass obj2(obj1); MyClass obj3(my_move(obj1)); MyClass obj4; MyClass obj5 = obj4; MyClass obj6 = my_move(obj4); return 0; }
|
这个实现展示了 std::move 的核心原理:
- 引用消除:通过自定义的 my_remove_reference 模板来去除类型的引用属性
- 右值转换:将输入的参数转换为其去除引用后的右值引用类型
- 类型擦除:保留原始类型的语义,仅改变其引用属性
实现要点:
- my_remove_reference 处理各种引用类型(左值引用、右值引用)
- my_move 函数使用 static_cast 将对象转换为右值引用
- 保持 noexcept 属性,符合移动操作的异常规范
示例中的 MyClass 演示了 std::move 在实际使用中的效果,显示了何时调用拷贝构造函数,何时调用移动构造函数。
std::move 是 C++11 引入的重要特性之一,它使得移动语义和完美转发成为可能,极大地提升了 C++ 程序的性能。
用 C++ 实现一个日志系统,可以被调用,要求性能最好。先思考三分钟,说一下思路,伪代码实现。
设计一个高性能的日志系统,需要考虑几个关键点:
- 线程安全
- 最小化日志记录对主程序性能的影响
- 灵活的日志级别支持
- 易于扩展和维护。
以下是实现这样一个系统的思路:
思路
-
日志级别:定义不同的日志级别(如 DEBUG、INFO、WARNING、ERROR),让用户能够根据需要控制输出的详细程度。
-
异步日志记录:为了避免在记录日志时阻塞主线程,采用生产者 - 消费者模式,使用队列来缓存日志消息,并在一个单独的线程中处理这些消息。
-
高效写入:为了提高写入效率,可以将多个日志条目批量写入到文件中,减少磁盘 I/O 操作次数。
-
线程安全:确保多线程环境下的安全性,特别是当有多个线程同时尝试记录日志时。
-
格式化与过滤:提供灵活的日志格式化选项和过滤机制,允许用户自定义哪些级别的日志应当被记录下来。
伪代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <string> #include <vector>
enum LogLevel { DEBUG, INFO, WARNING, ERROR };
class LogMessage { public: LogLevel level; std::string content; };
class Logger { private: std::queue<LogMessage> logQueue; std::mutex queueMutex; std::condition_variable cv; std::thread workerThread; bool running = true;
void worker() { while (running || !logQueue.empty()) { std::unique_lock<std::mutex> lock(queueMutex); cv.wait(lock, [this] { return !running || !logQueue.empty(); }); while (!logQueue.empty()) { auto logMsg = logQueue.front(); logQueue.pop(); writeToFile(logMsg); } } }
void writeToFile(const LogMessage& msg) { }
public: Logger() { workerThread = std::thread(&Logger::worker, this); }
~Logger() { { std::lock_guard<std::mutex> lock(queueMutex); running = false; } cv.notify_all(); workerThread.join(); }
void log(LogLevel level, const std::string& message) { if (level >= currentLevel) { std::lock_guard<std::mutex> lock(queueMutex); logQueue.emplace(LogMessage{level, message}); cv.notify_one(); } }
LogLevel currentLevel = INFO; };
Logger logger; logger.log(INFO, "This is an info message.");
|
这个伪代码展示了如何创建一个简单的异步日志系统。实际应用中可能还需要增加更多的功能,比如配置文件的支持、多种输出目标(文件、控制台等)、更复杂的格式化选项等。此外,还可以通过调整队列大小、批量写入策略等方式进一步优化性能。
多线程打印奇偶数
三线程依次打印 1~100
实现一个支持 TTL(Time To Live)和支持任意类型值的 K-V 存储数据库。
解决方案包括:map + 自定义 entry + 懒惰删除 + 定期任务 + 线程池。
参考文献
校招 C++ 大概学习到什么程度