自定义SharedPointer中预分配内存的placement new对齐问题问询
关于共享指针实现中的内存对齐问题解答
首先明确回答:必须要为分配的内存设置正确的对齐,否则对于需要严格对齐的类型(比如SSE数据类型、包含指针的复杂结构体等),会直接触发未定义行为,轻则程序运行异常,重则崩溃。
你的初始实现的问题
你最初直接用uint8_t* buffer = new uint8_t[sizeof(T) + sizeof(unsigned int)];分配内存,这里的buffer对齐是uint8_t的1字节对齐,但T可能需要更高的对齐要求(比如8字节、16字节)。直接在这种低对齐的内存上用placement new构造T,完全不符合C++标准中对象对齐的要求,属于未定义行为。
你更新后的std::aligned_storage方案的问题
思路是对的,但细节上有错误:
- counter的位置计算错误:你用
p.buffer + sizeof(T)来定位counter的地址,但std::aligned_storage_t<sizeof(T), alignof(T)>的大小不一定等于sizeof(T)——它会自动填充到alignof(T)的倍数,确保对齐。而且结构体Storage中的counter成员的位置是编译器自动处理对齐填充后的位置,不是简单的sizeof(T)偏移。 - 不必要的placement new:
new Storage()已经为counter分配了内存,直接赋值即可,不需要再用placement new初始化,反而容易出错。
修正后的正确实现
这里给出完善后的代码,既保证对齐安全,又实现共享指针的核心功能:
#include <utility> #include <type_traits> template<typename T> class SharedPointer { // 用结构体打包对象存储和引用计数,编译器自动处理对齐 struct Storage { std::aligned_storage_t<sizeof(T), alignof(T)> obj_storage; unsigned int counter; }; Storage* buffer; T* obj; unsigned int* counter; // 私有构造函数,强制通过make工厂函数创建实例 SharedPointer() : buffer(nullptr), obj(nullptr), counter(nullptr) {} public: template<typename... Args> static SharedPointer make(Args&&... args) { SharedPointer p; // 分配Storage内存,编译器自动保证整个结构体的对齐(取成员最大对齐要求) p.buffer = new Storage(); // 在对齐后的存储区构造T对象 p.obj = new (&p.buffer->obj_storage) T(std::forward<Args>(args)...); // 直接引用结构体中的counter,无需手动计算偏移 p.counter = &p.buffer->counter; // 初始化引用计数为1 *p.counter = 1; return p; } // 拷贝构造:共享资源,引用计数+1 SharedPointer(const SharedPointer& other) : buffer(other.buffer), obj(other.obj), counter(other.counter) { if (counter) { (*counter)++; } } // 移动构造:接管资源,原对象置空 SharedPointer(SharedPointer&& other) noexcept : buffer(other.buffer), obj(other.obj), counter(other.counter) { other.buffer = nullptr; other.obj = nullptr; other.counter = nullptr; } // 拷贝赋值:先销毁当前资源,再共享新资源 SharedPointer& operator=(const SharedPointer& other) { if (this != &other) { cleanup(); buffer = other.buffer; obj = other.obj; counter = other.counter; if (counter) { (*counter)++; } } return *this; } // 移动赋值:接管资源,原对象置空 SharedPointer& operator=(SharedPointer&& other) noexcept { if (this != &other) { cleanup(); buffer = other.buffer; obj = other.obj; counter = other.counter; other.buffer = nullptr; other.obj = nullptr; other.counter = nullptr; } return *this; } ~SharedPointer() { cleanup(); } // 重载解引用和指针运算符 T& operator*() const { return *obj; } T* operator->() const { return obj; } // 获取当前引用计数 unsigned int use_count() const { return counter ? *counter : 0; } private: // 提取销毁逻辑,避免重复代码 void cleanup() { if (counter) { (*counter)--; if (*counter == 0) { // 显式析构T对象 obj->~T(); // 释放整个Storage内存 delete buffer; } } } };
关键修正点说明
- 结构体自动对齐:
Storage结构体的对齐由编译器自动处理,取obj_storage(对齐要求为alignof(T))和counter(对齐要求为alignof(unsigned int))中的最大值,确保整个内存块的对齐满足所有成员的要求。 - 避免手动偏移计算:直接通过
&buffer->counter获取引用计数的地址,完全依赖编译器的内存布局处理,杜绝了对齐错误。 - 完善的资源管理:补充了拷贝/移动构造、赋值运算符,遵循RAII原则,避免内存泄漏和悬空指针。
另一种可选方案(手动对齐分配)
如果你不想用结构体打包,也可以用std::aligned_alloc手动分配对齐内存:
template<typename... Args> static SharedPointer make(Args&&... args) { SharedPointer p; // 计算需要的最大对齐值 constexpr size_t max_align = std::max(alignof(T), alignof(unsigned int)); // 分配对齐后的内存 p.buffer = static_cast<uint8_t*>(std::aligned_alloc(max_align, sizeof(T) + sizeof(unsigned int))); // 构造T对象(buffer已经满足alignof(T)) p.obj = new (p.buffer) T(std::forward<Args>(args)...); // 计算counter的对齐位置:跳过sizeof(T)后,填充到alignof(unsigned int)的倍数 size_t counter_offset = ((sizeof(T) + max_align - 1) / max_align) * max_align; p.counter = new (p.buffer + counter_offset) unsigned int(1); return p; }
这种方式需要手动计算对齐偏移,不如结构体方案简洁可靠,一般不推荐。
内容的提问来源于stack exchange,提问作者csk
相关产品推荐
相关产品推荐

