多态是 C++ 面向对象编程的核心特性之一,它使得基类指针能够指向任何派生类对象,从而实现统一接口的灵活调用。与之配套的还有智能指针(如 std::shared_ptr、std::unique_ptr 等),它们负责对象生命周期管理,减少手动 delete 的错误。本文将演示如何编写一个支持多态的自定义智能指针 PolyPtr,并展示其在实际项目中的使用场景。
1. 设计目标
- 多态支持:能安全地存放任何继承自同一基类的对象。
- 引用计数:实现共享所有权(类似
std::shared_ptr)。 - 内存对齐与对齐优化:考虑结构体对齐,避免不必要的内存占用。
- 简洁接口:支持
operator*、operator->、use_count()、reset()等常用操作。
2. 基本实现思路
- 使用一个内部控制块(control block)来存放引用计数与指针。
- 控制块内的指针采用
void*,在解引用时通过模板参数恢复类型。 - 采用
std::atomic处理多线程计数。
#include <atomic>
#include <memory>
#include <type_traits>
template<typename Base>
class PolyPtr {
private:
struct ControlBlock {
std::atomic <size_t> ref_count;
void* ptr; // 指向对象
void (*deleter)(void*); // 自定义删除函数
ControlBlock(void* p, void (*del)(void*)) : ref_count(1), ptr(p), deleter(del) {}
};
ControlBlock* ctrl;
public:
// 默认构造
PolyPtr() noexcept : ctrl(nullptr) {}
// 从原始指针构造,传入自定义删除器
template<typename T,
typename = std::enable_if_t<std::is_base_of_v<Base, T>>>
explicit PolyPtr(T* ptr)
: ctrl(new ControlBlock(ptr, [](void* p){ delete static_cast<T*>(p); })) {}
// 拷贝构造
PolyPtr(const PolyPtr& other) noexcept : ctrl(other.ctrl) {
if (ctrl) ++ctrl->ref_count;
}
// 移动构造
PolyPtr(PolyPtr&& other) noexcept : ctrl(other.ctrl) {
other.ctrl = nullptr;
}
// 析构
~PolyPtr() {
release();
}
// 拷贝赋值
PolyPtr& operator=(const PolyPtr& other) noexcept {
if (this != &other) {
release();
ctrl = other.ctrl;
if (ctrl) ++ctrl->ref_count;
}
return *this;
}
// 移动赋值
PolyPtr& operator=(PolyPtr&& other) noexcept {
if (this != &other) {
release();
ctrl = other.ctrl;
other.ctrl = nullptr;
}
return *this;
}
// 解引用
Base& operator*() const noexcept { return *static_cast<Base*>(ctrl->ptr); }
Base* operator->() const noexcept { return static_cast<Base*>(ctrl->ptr); }
// 访问计数
size_t use_count() const noexcept { return ctrl ? ctrl->ref_count.load() : 0; }
// 重置
void reset() noexcept { release(); }
private:
void release() {
if (ctrl) {
if (--ctrl->ref_count == 0) {
ctrl->deleter(ctrl->ptr);
delete ctrl;
}
ctrl = nullptr;
}
}
};
3. 关键点说明
-
自定义删除器
通过ControlBlock内部的deleter指针实现不同派生类的正确析构。使用static_cast<T*>确保类型安全。 -
引用计数
` 保证多线程安全。若项目仅单线程,亦可改为普通 `size_t`。
`std::atomic -
对齐
ControlBlock中的ptr与deleter同属于对齐字段,整个结构体一般占用 16~24 字节,已足够高效。
4. 实际使用示例
struct Shape {
virtual void draw() const = 0;
virtual ~Shape() = default;
};
struct Circle : Shape {
void draw() const override { std::cout << "Circle\n"; }
};
struct Square : Shape {
void draw() const override { std::cout << "Square\n"; }
};
int main() {
PolyPtr <Shape> p1(new Circle);
PolyPtr <Shape> p2 = p1; // 共享同一 Circle
std::cout << "use_count: " << p1.use_count() << '\n'; // 2
p1->draw(); // 调用 Circle::draw
p2.reset(); // 释放 Circle,计数变为 1
PolyPtr <Shape> p3(new Square);
std::cout << "use_count: " << p3.use_count() << '\n'; // 1
}
5. 与 std::shared_ptr 的对比
| 特性 | PolyPtr |
std::shared_ptr |
|---|---|---|
| 继承多态 | 支持自定义删除器,保证基类指针安全解引用 | 内置多态支持,无需额外删除器 |
| 内存占用 | 约 16-24 字节 | 约 16 字节 |
| 线程安全 | 采用 atomic |
线程安全 |
| 使用成本 | 需要自己实现 | 标准库已完成 |
结论:若项目需要对每个对象使用不同的析构逻辑,或者想要控制内部细节(如自定义内存池),
` 更加便捷。PolyPtr是一种可行的方案。否则直接使用 `std::shared_ptr
6. 小结
本文介绍了如何在 C++ 中实现支持多态的自定义智能指针 PolyPtr。通过内部控制块和自定义删除器,PolyPtr 兼具引用计数和多态特性,并提供了与 std::shared_ptr 类似的接口。掌握此类实现思路,可以帮助你在需要细粒度资源管理或自定义析构逻辑的项目中更加灵活地使用智能指针。