You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中如何使SmartBuffer返回的指针仅内部可删除,外部无法删除?

解决方案:限制MyArray指针的删除权限,仅让SmartBuffer负责销毁

先把你的原始SmartBuffer代码格式化一下,方便参考:

template<typename T> 
class SmartBuffer { 
    int capacity; 
    MyArray<T> *data; 
public: 
    SmartBuffer() { capacity = 0; data = nullptr; } 
    void alloc(int nbElements) { 
        if (nbElements > capacity) { 
            cout << "Allocating smart buffer\n"; 
            if (data!=nullptr) delete data; 
            data = new MyArray<T>(nbElements + 1); 
            capacity = nbElements + 1; 
        } 
    } 
    int size() { return capacity; } 
    MyArray<T>* getBuffer() const { return data; } 
    MyArray<T>* getBuffer() { return data; } 
};

你的核心需求是:调用者能修改MyArray对象,但绝对不能删除它,只有SmartBuffer有权销毁内部的MyArray实例。除了用包装类禁用delete操作符之外,咱们还有几种更贴合C++ idiom的实现方式,各有优劣,你可以根据场景选择:


方法1:给MyArray设置私有析构函数,仅让SmartBuffer成为友元

如果MyArray是你能控制的类(不是第三方库),这是最直接的强制约束方案。把MyArray的析构函数设为私有,然后让SmartBuffer成为它的友元,这样只有SmartBuffer能销毁MyArray实例,调用者哪怕拿到指针也无法执行delete(编译器直接报错)。

修改后的MyArray示例:

template<typename T>
class MyArray {
    // 把析构函数设为私有
    ~MyArray() = default;
    // 让对应的SmartBuffer能访问析构函数
    friend class SmartBuffer<T>;
public:
    // 你的MyArray原有公共接口,比如构造函数、元素访问方法等
    explicit MyArray(int size) { /* ... */ }
    T& operator[](int idx) { /* ... */ }
    const T& operator[](int idx) const { /* ... */ }
};

优点:完全由编译器保证安全,没有任何运行时开销;
缺点:必须修改MyArray的代码,如果MyArray是第三方类就没法用。


方法2:返回带自定义空删除器的std::unique_ptr

不需要修改MyArray,咱们可以让SmartBuffer返回一个不拥有所有权的智能指针——用std::unique_ptr搭配一个什么都不做的删除器。这样调用者拿到的智能指针可以正常操作MyArray,但无论如何都不会触发delete,而SmartBuffer内部保留真正的拥有型指针来管理生命周期。

首先定义空删除器:

struct NoOpDeleter {
    template<typename U>
    void operator()(U*) const noexcept {
        // 空实现:什么都不做,禁止删除操作
    }
};

然后修改SmartBuffer的getBuffer方法:

template<typename T> 
class SmartBuffer { 
    int capacity; 
    // 内部用带默认删除器的unique_ptr来拥有数据
    std::unique_ptr<MyArray<T>> data; 
public: 
    SmartBuffer() : capacity(0), data(nullptr) {} 
    void alloc(int nbElements) { 
        if (nbElements > capacity) { 
            cout << "Allocating smart buffer\n"; 
            data = std::make_unique<MyArray<T>>(nbElements + 1); 
            capacity = nbElements + 1; 
        } 
    } 
    int size() { return capacity; } 

    // 返回不拥有所有权的unique_ptr
    std::unique_ptr<MyArray<T>, NoOpDeleter> getBuffer() {
        return std::unique_ptr<MyArray<T>, NoOpDeleter>(data.get(), NoOpDeleter{});
    }

    const std::unique_ptr<const MyArray<T>, NoOpDeleter> getBuffer() const {
        return std::unique_ptr<const MyArray<T>, NoOpDeleter>(data.get(), NoOpDeleter{});
    } 
};

优点:不需要修改MyArray,调用者拿到的是智能指针,语义清晰(明确是不拥有的);即使调用者手动调用reset()或者让智能指针销毁,也不会删除数据;
缺点:如果调用者刻意取出原始指针(通过get()方法)并执行delete,还是能绕过约束——但这属于恶意违反约定,正常场景下可以通过文档明确禁止。


方法3:使用代理类(Proxy)完全隐藏原始指针

咱们可以写一个轻量的代理类,让SmartBuffer返回这个代理而非原始指针。代理类重载operator->和operator*,让调用者能像使用指针一样操作MyArray,但完全不暴露原始指针,从根源上杜绝调用者执行delete的可能。

代理类示例:

template<typename T>
class MyArrayProxy {
private:
    MyArray<T>* ptr;
    // 禁止默认构造,只能由SmartBuffer创建
    MyArrayProxy() = delete;
public:
    // 仅允许从原始指针构造,且只能由友元SmartBuffer调用
    explicit MyArrayProxy(MyArray<T>* p) : ptr(p) {}
    // 重载箭头和星号运算符,模拟指针行为
    MyArray<T>* operator->() noexcept { return ptr; }
    const MyArray<T>* operator->() const noexcept { return ptr; }
    MyArray<T>& operator*() noexcept { return *ptr; }
    const MyArray<T>& operator*() const noexcept { return *ptr; }

    // 禁止转换为原始指针,彻底杜绝delete的可能
    operator MyArray<T>*() = delete;
    operator const MyArray<T>*() const = delete;

    // 让SmartBuffer能创建代理
    friend class SmartBuffer<T>;
};

然后修改SmartBuffer的getBuffer方法:

template<typename T> 
class SmartBuffer { 
    int capacity; 
    MyArray<T>* data; 
public: 
    // ... 其他代码不变 ...

    MyArrayProxy<T> getBuffer() {
        return MyArrayProxy<T>(data);
    }

    const MyArrayProxy<T> getBuffer() const {
        return MyArrayProxy<T>(data);
    }
};

调用者的使用方式几乎和原来一样:

SmartBuffer<int> buf;
buf.alloc(10);
auto proxy = buf.getBuffer();
proxy->operator[](0) = 42; // 正常修改数据
// delete proxy; // 编译器报错,proxy不是指针
// delete &*proxy; // 编译器报错,因为代理禁止转换为原始指针

优点:最安全,完全杜绝了调用者删除数据的可能;不需要修改MyArray;
缺点:需要额外写代理类的代码,稍微增加一点代码量,但代理类逻辑非常简单,几乎没有维护成本。


方法4:文档约定+语义化命名(软约束)

如果你的团队开发规范很严格,也可以选择最简单的方式:保留原始的指针返回,但给方法改个语义明确的名字(比如getMutableBuffer()),并在文档和注释里明确强调:“此指针仅用于修改数据,绝对不能执行delete操作,生命周期由SmartBuffer管理”。

优点:零代码修改,实现成本最低;
缺点:完全依赖调用者遵守约定,没有编译器强制约束,适合信任度高的内部团队场景。


内容的提问来源于stack exchange,提问作者Duke Le

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:27:44