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

如何改进自定义Lazy Shared Pointer类以支持拷贝赋值运算符

解决LazySharedPtr支持拷贝共享的问题

你的问题核心是要让多个LazySharedPtr实例共享同一个延迟初始化的对象,而不是各自持有独立的初始化逻辑和对象指针。你提到的std::shared_ptr<std::shared_ptr<T>>思路完全可行,而且是实现这个需求的直接方案之一,另外还有一种更结构化的控制块封装方案,我来详细拆解这两种思路:


方案一:嵌套std::shared_ptr(你考虑的思路)

把内部的std::shared_ptr<T>改成std::shared_ptr<std::shared_ptr<T>>,这样多个LazySharedPtr拷贝时,共享的是外层的shared_ptr——这个外层指针指向的是一个存储实际对象指针的shared_ptr<T>。当任意一个LazySharedPtr触发初始化时,都会给这个内层的shared_ptr<T>赋值,所有共享外层指针的实例都会看到初始化后的对象。

修改后的核心代码示例:

template <class T>
class LazySharedPtr{
public:
    static LazySharedPtr<T> Create(){
        std::function<std::shared_ptr<T>()> customInit = [](){ return std::make_shared<T>(); };
        LazySharedPtr<T> instance;
        instance.init = customInit;
        // 初始化外层shared_ptr,指向一个空的内层shared_ptr<T>
        instance.ptr_wrapper = std::make_shared<std::shared_ptr<T>>();
        return instance;
    };

    template <typename ... Args>
    static LazySharedPtr<T> Create(Args ... args){
        LazySharedPtr<T> instance;
        instance.init = [args = std::make_tuple(std::forward<Args>(args) ...)]() mutable {
            return std::apply(std::make_shared<T, Args...>, std::move(args));
        };
        instance.ptr_wrapper = std::make_shared<std::shared_ptr<T>>();
        return instance;
    };

    // 现在可以安全启用拷贝构造和拷贝赋值
    LazySharedPtr(const LazySharedPtr& other) = default;
    LazySharedPtr& operator=(const LazySharedPtr& other) = default;

    // 移动构造保持不变
    template <typename Y>
    LazySharedPtr(LazySharedPtr<Y> && other) 
        : init(std::move(other.init)), ptr_wrapper(std::move(other.ptr_wrapper)) {
        other.init = nullptr;
    };

    // 重载运算符的逻辑微调,访问内层shared_ptr
    T* operator->(){ return InitAndGet().get(); }
    const T* operator->() const{ return InitAndGet().get(); }
    T& operator*(){ return *InitAndGet(); } // 修正原代码返回指针的问题,改为返回引用更符合智能指针语义
    const T& operator*() const{ return *InitAndGet(); }
    explicit operator bool() const noexcept{ return (*ptr_wrapper != nullptr); }
    explicit operator std::shared_ptr<T>() const{ return InitAndGet(); }

    template <typename U>
    friend class LazySharedPtr;
protected:
    std::function<std::shared_ptr<T>()> init;
    mutable std::shared_ptr<std::shared_ptr<T>> ptr_wrapper;

    std::shared_ptr<T>& InitAndGet() const {
        if (!*ptr_wrapper) {
            // 触发初始化,赋值给内层shared_ptr
            *ptr_wrapper = init();
        }
        return *ptr_wrapper;
    }
};

为什么这个方案可行?

  • 外层的shared_ptr<std::shared_ptr<T>>自动管理引用计数,多个LazySharedPtr拷贝时共享同一个外层指针,自然实现了状态共享。
  • 初始化逻辑只在第一次调用InitAndGet时执行一次,所有实例都会复用这个初始化后的对象。

方案二:封装独立控制块

如果觉得嵌套shared_ptr不够直观,可以把初始化逻辑和对象指针封装到一个单独的控制块结构体中,用std::shared_ptr管理这个控制块。这种方式结构更清晰,方便后续扩展(比如添加线程安全锁、统计信息等)。

核心代码示例:

// 定义控制块,封装初始化逻辑和对象指针
template <class T>
struct LazyControlBlock {
    std::function<std::shared_ptr<T>()> init_func;
    std::shared_ptr<T> object_ptr;
    // 如果需要线程安全,可以添加mutex:std::mutex init_mutex;
};

template <class T>
class LazySharedPtr{
public:
    static LazySharedPtr<T> Create(){
        std::function<std::shared_ptr<T>()> customInit = [](){ return std::make_shared<T>(); };
        return LazySharedPtr(customInit);
    };

    template <typename ... Args>
    static LazySharedPtr<T> Create(Args ... args){
        auto init_func = [args = std::make_tuple(std::forward<Args>(args) ...)]() mutable {
            return std::apply(std::make_shared<T, Args...>, std::move(args));
        };
        return LazySharedPtr(init_func);
    };

    // 启用拷贝构造和赋值
    LazySharedPtr(const LazySharedPtr& other) = default;
    LazySharedPtr& operator=(const LazySharedPtr& other) = default;

    // 移动构造
    LazySharedPtr(LazySharedPtr&& other) noexcept 
        : control_block(std::move(other.control_block)) {};
    LazySharedPtr& operator=(LazySharedPtr&& other) noexcept {
        control_block = std::move(other.control_block);
        return *this;
    };

    // 运算符重载
    T* operator->(){ return InitAndGet().get(); }
    const T* operator->() const{ return InitAndGet().get(); }
    T& operator*(){ return *InitAndGet(); }
    const T& operator*() const{ return *InitAndGet(); }
    explicit operator bool() const noexcept{ return control_block->object_ptr != nullptr; }
    explicit operator std::shared_ptr<T>() const{ return InitAndGet(); }

    template <typename U>
    friend class LazySharedPtr;
protected:
    std::shared_ptr<LazyControlBlock<T>> control_block;

    explicit LazySharedPtr(std::function<std::shared_ptr<T>()> init_func) 
        : control_block(std::make_shared<LazyControlBlock<T>>()) {
        control_block->init_func = std::move(init_func);
    };

    std::shared_ptr<T>& InitAndGet() const {
        if (!control_block->object_ptr) {
            // 如果需要线程安全,这里加锁:std::lock_guard<std::mutex> lock(control_block->init_mutex);
            // 双重检查锁(如果加锁的话):if (!control_block->object_ptr) { ... }
            control_block->object_ptr = control_block->init_func();
        }
        return control_block->object_ptr;
    }
};

这个方案的优势:

  • 把延迟初始化的相关状态(初始化函数、对象指针、甚至锁)都封装在一起,代码可读性和可维护性更好。
  • 扩展能力强,比如要加线程安全,只需要在控制块里加mutex即可,不需要修改LazySharedPtr的核心逻辑。

额外注意点:线程安全

如果你的LazySharedPtr会在多线程环境下使用,上面的两种方案都需要添加线程安全保护,否则多个线程同时调用InitAndGet时可能会多次执行初始化函数。可以在InitAndGet中加入双重检查锁(DCLP)来避免这个问题,比如在控制块里添加std::mutex,然后在初始化前加锁并再次检查对象是否已初始化。


内容的提问来源于stack exchange,提问作者Martin Perry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 06:07:27