采用智能指针分配内存、原始指针访问的FilterData类设计是否合理?
这种混合设计完全合理,且是务实的选择
首先得说,你的这个设计思路不仅合理,还很务实——既用智能指针保证了FilterData对内存的绝对所有权,又兼顾了和旧API的对接需求,同时考虑到了不同水平开发者的使用场景。我一点都不觉得这种“混合”方式会让人困惑,只要接口设计和注释到位,反而能让逻辑更清晰。
为什么这种设计没问题?
- 所有权边界清晰:
FilterData用std::unique_ptr持有数据,明确了它是内存的唯一所有者。返回原始指针时,只要通过命名(比如getXXX)和const修饰传递“这只是临时访问,你不拥有它”的信号,哪怕是不熟悉智能指针的开发者,也能快速理解规则。 - 对接旧API的必要性:现有API依赖原始
float*,这是你无法回避的约束。直接返回智能指针反而会给调用者增加不必要的学习成本,甚至需要他们额外做转换,反而降低了易用性。 - 副本方案补全了场景:你计划添加返回
std::unique_ptr副本的函数,这完美覆盖了“需要独立数据”的场景——想要临时访问的用原始指针,想要拥有自己数据的拿unique_ptr,两种需求区分明确,不会混淆。
如何降低潜在风险?
你担心有人会对返回的指针调用delete,这个风险确实存在,但可以通过几个小调整把它降到最低:
- 直接返回数组指针而非容器指针:你的示例里返回的是
const fvec*(也就是const std::vector<float>*),调用者如果误删这个指针,会直接销毁vector对象,触发double free。不如改成返回vector内部的数组指针,同时补充大小接口:
这样调用者拿到的是纯粹的const float* getFilterACoeffsData() const { return m_filterACoeffs->data(); } size_t getFilterACoeffsSize() const { return m_filterACoeffs->size(); }float*,更贴合旧API的需求,同时他们几乎不会想到去delete这个数组指针(毕竟没人会随便delete一个API返回的数组指针)。 - 强化注释提示:在每个get函数的注释里明确写清楚:“返回的指针由FilterData实例拥有,请勿调用delete/free,且仅在FilterData实例存活期间有效”。对不熟悉智能指针的开发者来说,直白的文字提示比任何设计模式都管用。
- 保持返回值的const性:你已经用
const修饰返回的指针,这很好——它传递了“只读”的信号,不仅能防止调用者意外修改数据,也能降低他们试图销毁内存的冲动(毕竟const对象通常不会被delete)。
优化后的示例代码参考
#include <iostream> #include <vector> #include <string> #include <memory> class FilterData { using fvec = std::vector<float>; public: FilterData(const std::string& filename) { // (simulate read from file...) m_filterACoeffs = std::make_unique<fvec>(); m_filterBCoeffs = std::make_unique<fvec>(); m_filterACoeffs->emplace_back(1.f); m_filterACoeffs->emplace_back(2.f); m_filterACoeffs->emplace_back(3.f); m_filterBCoeffs->emplace_back(-1.f); m_filterBCoeffs->emplace_back(-2.f); m_filterBCoeffs->emplace_back(-3.f); } // 供旧API使用:返回只读的数组指针和大小 const float* getFilterACoeffsData() const { return m_filterACoeffs->data(); } size_t getFilterACoeffsSize() const { return m_filterACoeffs->size(); } const float* getFilterBCoeffsData() const { return m_filterBCoeffs->data(); } size_t getFilterBCoeffsSize() const { return m_filterBCoeffs->size(); } // 供需要副本的调用者:转移副本的所有权 std::unique_ptr<fvec> cloneFilterACoeffs() const { return std::make_unique<fvec>(*m_filterACoeffs); } std::unique_ptr<fvec> cloneFilterBCoeffs() const { return std::make_unique<fvec>(*m_filterBCoeffs); } private: std::unique_ptr<fvec> m_filterACoeffs; std::unique_ptr<fvec> m_filterBCoeffs; }; int main() { FilterData filterData("thefile.bin"); // 对接旧API的场景 const float* aCoeffs = filterData.getFilterACoeffsData(); size_t aSize = filterData.getFilterACoeffsSize(); std::cout << "Filter A is: "; for (size_t i = 0; i < aSize; ++i) { std::cout << aCoeffs[i] << " "; } std::cout << std::endl; // 需要副本的场景 auto clonedACoeffs = filterData.cloneFilterACoeffs(); std::cout << "Cloned Filter A first element: " << clonedACoeffs->at(0) << std::endl; }
总的来说,你的设计思路是非常成熟的——既保证了内部的内存安全,又对外提供了符合不同需求的接口,完全不用担心“混合”方式会带来困惑,只要把细节(比如注释、返回值类型)处理好,就是一个很棒的设计。
内容的提问来源于stack exchange,提问作者Sidelobe
相关产品推荐
相关产品推荐

