如何改进自定义Lazy Shared Pointer类以支持拷贝赋值运算符
你的问题核心是要让多个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
相关产品推荐
相关产品推荐

