在 C++11 之后,标准库已经提供了 std::unique_ptr、std::shared_ptr 和 std::weak_ptr 等智能指针,它们大大简化了资源管理的难度。然而在某些特殊场景下,我们可能需要一个既能像 std::unique_ptr 那样保证唯一所有权,又能在内部实现自定义行为(例如日志、引用计数的定制、资源池的回收等)的智能指针。下面我们以实现一个名为 SimpleUniquePtr 的自定义智能指针为例,展示其设计思路、核心实现细节以及使用示例。
1. 需求分析
| 功能 | 说明 |
|---|---|
| 1. 唯一所有权 | 只能有一个 SimpleUniquePtr 拥有该资源,复制构造/赋值禁止 |
| 2. 自动析构 | 当指针离开作用域时自动释放资源 |
| 3. 自定义析构器 | 可以在构造时传入自定义的析构函数 |
| 4. 日志跟踪 | 每次资源获取、释放时打印日志 |
| 5. 可自定义内存分配 | 允许使用自定义的 new/delete 组合 |
2. 设计要点
- 内部结构:
SimpleUniquePtr只保存一个原始指针T* ptr_和一个析构函数std::function<void(T*)> deleter_。构造时若未传入deleter_,则默认使用delete。 - 移动语义:实现移动构造和移动赋值操作,保证资源所有权转移时原指针置为空。
- 禁止拷贝:拷贝构造和拷贝赋值都被删除,防止出现多重所有权。
- 日志:在构造、析构、移动等关键时刻打印日志,便于调试和性能分析。
- 自定义分配器:通过
allocate静态成员模板提供对资源的分配,并在SimpleUniquePtr中持有Allocator*(如果需要)。
3. 核心代码
#include <iostream>
#include <functional>
#include <memory>
#include <utility>
#include <chrono>
#include <iomanip>
template <typename T>
class SimpleUniquePtr {
public:
// 默认析构器
using Deleter = std::function<void(T*)>;
// 构造:接受裸指针和可选析构器
explicit SimpleUniquePtr(T* ptr = nullptr, Deleter deleter = nullptr)
: ptr_(ptr), deleter_(deleter ? std::move(deleter) : DefaultDeleter()) {
log("Constructed", ptr_);
}
// 移动构造
SimpleUniquePtr(SimpleUniquePtr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr;
log("Move Constructed", ptr_);
}
// 移动赋值
SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
if (this != &other) {
reset(); // 先释放自身资源
ptr_ = other.ptr_;
deleter_ = std::move(other.deleter_);
other.ptr_ = nullptr;
log("Move Assigned", ptr_);
}
return *this;
}
// 禁止拷贝
SimpleUniquePtr(const SimpleUniquePtr&) = delete;
SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;
// 析构
~SimpleUniquePtr() {
reset();
log("Destructed", ptr_);
}
// 访问
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
// 手动释放
void reset(T* ptr = nullptr, Deleter deleter = nullptr) {
if (ptr_) {
deleter_(ptr_);
log("Reset", ptr_);
}
ptr_ = ptr;
deleter_ = deleter ? std::move(deleter) : DefaultDeleter();
}
// 取出裸指针(不释放)
T* release() {
T* old = ptr_;
ptr_ = nullptr;
log("Released", old);
return old;
}
private:
static Deleter DefaultDeleter() {
return [](T* p) { delete p; };
}
void log(const std::string& msg, T* ptr) const {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::tm tm;
localtime_r(&time, &tm);
std::cout << "[" << std::put_time(&tm, "%F %T") << "] " << msg << " (ptr=" << static_cast<void*>(ptr) << ")\n";
}
T* ptr_ = nullptr;
Deleter deleter_;
};
4. 使用示例
// 自定义资源
struct Resource {
int id;
Resource(int v) : id(v) { std::cout << "Resource(" << id << ") constructed.\n"; }
~Resource() { std::cout << "Resource(" << id << ") destroyed.\n"; }
};
int main() {
// 1. 默认析构器
SimpleUniquePtr <Resource> ptr1(new Resource(1));
// 2. 自定义析构器(比如回收到对象池)
auto poolDeleter = [](Resource* r) {
std::cout << "Returning Resource(" << r->id << ") to pool.\n";
delete r; // 这里仅做示例,实际可实现对象池
};
SimpleUniquePtr <Resource> ptr2(new Resource(2), poolDeleter);
// 3. 移动语义
SimpleUniquePtr <Resource> ptr3 = std::move(ptr1);
if (!ptr1.get()) std::cout << "ptr1 is empty after move.\n";
// 4. 手动释放
Resource* raw = ptr2.release(); // ptr2 失去所有权
std::cout << "Raw pointer id: " << raw->id << "\n";
delete raw; // 需要手动删除
return 0;
}
5. 进一步扩展
- 引用计数:可以在内部加入一个 `std::atomic ` 计数器,支持 `SimpleSharedPtr` 的实现。
- 多线程安全:使用
std::mutex或std::atomic保护内部状态,确保在多线程环境下移动、reset 等操作安全。 - 自定义分配器:引入 `std::allocator ` 或自定义 `Allocator` 类,让 `SimpleUniquePtr` 在 `new`/`delete` 之外使用预分配内存池。
6. 小结
SimpleUniquePtr通过组合原始指针和std::function实现自定义析构器。- 移动语义保证了唯一所有权,拷贝被删除以避免错误。
- 日志功能帮助跟踪资源生命周期,适合调试和性能分析。
- 可进一步扩展为多功能智能指针或与内存池配合使用。
自定义智能指针虽然在标准库已完备的情况下不常见,但在需要细粒度控制资源释放或在老旧项目中使用时,仍然是一个非常实用且灵活的工具。