为何无法返回std::lock_guard?如何实现无shared_ptr的替代方案?
问题核心分析
你写出的lock1()函数在部分编译器(如programiz)能运行,但在VS2017的C17环境下编译失败,提示“attempting to reference a deleted function”,本质原因是std::lock_guard的设计特性和**编译器对C17标准的支持差异**:
std::lock_guard是轻量级锁包装器,它的拷贝构造函数和移动构造函数均被显式删除——这是为了避免锁的所有权被意外转移,保证锁的生命周期严格绑定到当前对象。- C17标准引入了保证复制消除(Guaranteed Copy Elision),当函数返回临时对象时(如
return std::lock_guard<std::mutex>{m};),编译器可以直接在目标变量的内存位置构造对象,无需调用拷贝/移动构造函数。但VS2017对C17的这一特性支持不完整,仍会尝试调用拷贝构造函数,而lock_guard的拷贝构造已被删除,因此编译报错。其他编译器(如GCC/Clang)对该特性的支持更完善,所以能正常运行。
你无法用std::move解决这个问题,因为std::lock_guard根本没有移动构造函数——它的设计就是禁止所有权转移。
线程安全包装器的优化方案
你的ThreadSafePtr中被注释的Lock lock()版本编译失败,原因和上面一致:Lock类包含std::lock_guard成员,导致Lock的拷贝/移动构造函数被默认删除,VS2017无法通过复制消除完成返回操作。以下是两种无需std::shared_ptr的解决方案:
方案1:替换std::lock_guard为std::unique_lock
std::unique_lock是更灵活的锁包装器,支持移动构造(但仍禁止拷贝)。修改Lock类的成员类型后,Lock会自动生成移动构造函数,从而允许函数按值返回:
#include <mutex> #include <memory> #include <map> #include <utility> #include <thread> #include <type_traits> template<typename T> class ThreadSafePtr { std::shared_ptr<T> ptr; std::shared_ptr<std::mutex> mutex; public: // 添加SFINAE约束,避免模板构造函数与拷贝构造函数冲突 template<typename... Args, typename = std::enable_if_t<!std::is_convertible_v<std::decay_t<Args>..., ThreadSafePtr>>> ThreadSafePtr(Args&&... args) : ptr{ std::make_shared<T>(std::forward<Args>(args)...) }, mutex{ std::make_shared<std::mutex>() } {} ThreadSafePtr(const ThreadSafePtr& orig) = default; ThreadSafePtr& operator= (const ThreadSafePtr& orig) = default; class Lock { friend class ThreadSafePtr<T>; T& ref; std::unique_lock<std::mutex> lock; // 替换为unique_lock Lock(ThreadSafePtr* tsptr) : ref{ *tsptr->ptr }, lock{ *tsptr->mutex } {} public: T& get() const { return ref; } // 显式允许移动,禁止拷贝 Lock(Lock&&) = default; Lock& operator=(Lock&&) = default; Lock(const Lock&) = delete; Lock& operator=(const Lock&) = delete; }; // 恢复按值返回的版本 Lock lock() { return Lock{ this }; } }; int main() { ThreadSafePtr<std::map<int, int>> map; std::thread([map]() mutable { map.lock().get()[1] = 2; }).detach(); std::thread([map]() mutable { map.lock().get()[3] = 4; }).detach(); return 0; }
方案2:升级编译器版本
如果坚持使用std::lock_guard,可以将VS版本升级到2019及以后——这些版本完整支持C++17的保证复制消除特性,此时Lock lock() { return Lock{this}; }可以正常编译,编译器会直接在返回目标位置构造Lock对象,无需调用拷贝/移动构造函数。
关于ThreadSafePtr的其他编译问题
你提到的完美转发构造函数在lambda捕获时的编译错误,是因为模板构造函数与拷贝构造函数的重载解析冲突:当lambda捕获ThreadSafePtr对象时,模板构造函数的匹配优先级高于默认的拷贝构造函数,导致编译器尝试用完美转发构造而非拷贝构造。解决方法是给模板构造函数添加SFINAE约束(如方案1中所示),确保它不会被用于拷贝/移动ThreadSafePtr对象的场景。
内容的提问来源于stack exchange,提问作者H.v.M.

