在 C++ 标准库中,std::unique_ptr、std::shared_ptr 和 std::weak_ptr 为我们提供了强大的指针管理功能。然而,在某些特定场景下,可能需要一个更轻量级或更具业务特定功能的智能指针。本文将通过一个完整示例,演示如何从零实现一个自定义的“计数型”智能指针(类似 std::shared_ptr),并在此基础上添加日志记录、内存泄漏检测等扩展功能。
1. 设计目标
- 引用计数:当多个指针实例共享同一块内存时,计数递增;当所有指针销毁时,释放资源。
- 线程安全:计数操作使用
std::atomic以支持多线程环境。 - 日志功能:每次引用计数变化时输出日志,方便调试。
- 内存泄漏检测:通过全局计数器,跟踪所有活跃对象数,程序结束时检查是否为 0。
2. 基础结构
#include <iostream>
#include <atomic>
#include <mutex>
#include <cassert>
template <typename T>
class MySharedPtr {
private:
struct ControlBlock {
std::atomic <size_t> refCount;
T* ptr;
std::mutex mtx; // 用于日志同步
ControlBlock(T* p) : refCount(1), ptr(p) {}
~ControlBlock() { delete ptr; }
};
ControlBlock* ctrl;
// 全局活跃对象计数(用于泄漏检测)
static std::atomic <size_t> globalActive;
ControlBlock保存原始指针和引用计数,并在销毁时自动delete指针。globalActive用来跟踪所有MySharedPtr所持有的对象数量。
3. 构造与析构
public:
// 默认构造
MySharedPtr() : ctrl(nullptr) {}
// 通过裸指针构造
explicit MySharedPtr(T* rawPtr) {
if (rawPtr) {
ctrl = new ControlBlock(rawPtr);
++globalActive;
log("Constructed");
} else {
ctrl = nullptr;
}
}
// 拷贝构造
MySharedPtr(const MySharedPtr& other) : ctrl(other.ctrl) {
if (ctrl) {
++ctrl->refCount;
log("Copy constructed");
}
}
// 移动构造
MySharedPtr(MySharedPtr&& other) noexcept : ctrl(other.ctrl) {
other.ctrl = nullptr;
log("Move constructed");
}
// 析构
~MySharedPtr() {
release();
}
private:
void release() {
if (ctrl) {
if (--ctrl->refCount == 0) {
log("Deleting resource");
delete ctrl;
} else {
log("Released, refCount=" + std::to_string(ctrl->refCount));
}
ctrl = nullptr;
}
}
- 每个构造函数根据情况更新计数并记录日志。
release()在销毁时执行计数递减与资源释放。
4. 赋值操作
public:
// 拷贝赋值
MySharedPtr& operator=(const MySharedPtr& other) {
if (this != &other) {
release();
ctrl = other.ctrl;
if (ctrl) {
++ctrl->refCount;
log("Copy assigned");
}
}
return *this;
}
// 移动赋值
MySharedPtr& operator=(MySharedPtr&& other) noexcept {
if (this != &other) {
release();
ctrl = other.ctrl;
other.ctrl = nullptr;
log("Move assigned");
}
return *this;
}
5. 访问接口
public:
T& operator*() const { assert(ctrl && ctrl->ptr); return *(ctrl->ptr); }
T* operator->() const { assert(ctrl && ctrl->ptr); return ctrl->ptr; }
size_t use_count() const { return ctrl ? ctrl->refCount.load() : 0; }
explicit operator bool() const { return ctrl && ctrl->ptr; }
6. 日志与泄漏检测
private:
static void log(const std::string& msg) {
std::lock_guard<std::mutex> lock(logMtx);
std::cout << "[MySharedPtr] " << msg << "\n";
}
static std::mutex logMtx;
};
template<typename T>
std::atomic <size_t> MySharedPtr<T>::globalActive{0};
template<typename T>
std::mutex MySharedPtr <T>::logMtx;
logMtx用于同步日志输出,避免多线程混乱。- 在程序结束前可以检查
globalActive是否为 0。
int main() {
{
MySharedPtr <int> p1(new int(42));
{
MySharedPtr <int> p2 = p1;
std::cout << "p1 use_count: " << p1.use_count() << "\n";
}
std::cout << "p1 use_count after p2 out of scope: " << p1.use_count() << "\n";
}
std::cout << "globalActive after all: " << MySharedPtr<int>::globalActive << "\n";
return 0;
}
7. 进阶扩展
7.1 定制分配器
通过模板参数 Alloc 替换 new/delete,可支持自定义内存池。
template<typename T, typename Alloc = std::allocator<T>>
class MySharedPtr;
7.2 对齐与多继承
在 ControlBlock 内部加入 std::aligned_storage 或 std::max_align_t,以满足对齐需求。
7.3 异常安全
在构造时,如果 new 抛异常,计数不变,程序安全。
8. 小结
本文通过最小化的代码实现了一个具备引用计数、线程安全、日志记录和泄漏检测功能的自定义智能指针。该实现与 std::shared_ptr 在功能上相近,但提供了更多可自定义的钩子,适用于需要细粒度控制或特殊行为的项目。你可以在此基础上继续扩展,例如实现 weak_ptr、支持自定义 deleter、或与 RAII 容器协同工作,进一步提升代码质量与可维护性。