如何通过工厂函数实现对象的原位构造(emplace)?含std::mutex场景的实现原理与多方案解决思路需求
哈哈,这个问题问到点子上了!我刚看到你的代码时也有点惊讶——毕竟std::mutex是出了名的不能拷贝、不能移动的类型,居然能直接把工厂函数的返回值传给emplace?先给你拆解下背后的原理,再给你几个实用的替代方案,覆盖不同C++版本的场景~
先搞懂:你当前代码能跑的核心原理
你写的代码之所以合法,全靠C++17引入的**保证拷贝消除(Guaranteed Copy Elision)**特性:
当函数返回一个纯右值(比如直接构造的
std::mutex{})时,C++17及以后的标准强制要求编译器跳过所有拷贝/移动操作,直接在调用方的目标内存位置构造对象——哪怕这个类型没有拷贝/移动构造函数也完全没问题。
放到你的代码里看:std::mutex f() {return std::mutex{};}返回的是纯右值,m.emplace(f())调用时,编译器会直接在std::optional内部预留的内存空间里构造std::mutex,完全绕开了拷贝/移动的环节,所以哪怕std::mutex禁用了拷贝移动,这段代码也能正常编译运行。
多场景实用解决方案
方案一:直接利用C++17保证拷贝消除(最简洁)
这就是你现在用的方案,适合C++17及以上版本,只要工厂函数返回纯右值就可以直接用。要注意的是,工厂函数必须返回纯右值(比如直接构造对象返回),如果返回左值或局部变量的引用,这套就失效了。
代码示例:
#include <optional> #include <mutex> // 工厂函数返回纯右值,触发保证拷贝消除 std::mutex f() { return std::mutex{}; } int main() { std::optional<std::mutex> m; m.emplace(f()); // 直接在optional内部构造mutex,无拷贝/移动 }
方案二:用工厂函数转发构造参数(适配带参构造的类型)
如果你的工厂函数不是直接返回对象,而是生成对象的构造参数(比如自定义不可拷贝类型需要参数构造),可以直接把工厂函数的返回值转发给emplace,让emplace用这些参数原位构造对象。
代码示例(用自定义不可拷贝类型演示):
#include <optional> #include <functional> // 自定义不可拷贝移动的类型 struct NonCopyable { int id; explicit NonCopyable(int val) : id(val) {} NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete; }; // 工厂函数生成构造参数 int get_id() { return 10086; } int main() { std::optional<NonCopyable> opt; // 把工厂函数的返回值作为构造参数传给emplace opt.emplace(get_id()); }
方案三:显式原位构造(C++20+ 更灵活)
如果需要更显式地控制原位构造的逻辑,或者工厂函数是一个可调用对象,可以用std::construct_at直接在optional的内存空间里构造对象,逻辑更清晰。
代码示例:
#include <optional> #include <mutex> #include <memory> std::mutex create_mutex() { return std::mutex{}; } int main() { std::optional<std::mutex> m; if (!m) { // 显式在optional的内部内存位置构造mutex std::construct_at(m.emplace(), create_mutex()); } }
方案四:自定义原位构造包装器(适配C++11/14老版本)
如果你的编译器还不支持C++17的保证拷贝消除,可以写一个简单的包装器,让工厂函数在emplace的参数传递过程中,直接在目标内存位置构造对象,绕开拷贝/移动。
代码示例:
#include <optional> #include <mutex> #include <functional> // 自定义原位构造包装器 template<typename Func> struct InPlaceFactory { Func func; explicit InPlaceFactory(Func f) : func(std::move(f)) {} // 转换运算符:在目标位置调用工厂函数构造对象 template<typename T> operator T() && { return std::move(func)(); } }; // 辅助函数简化包装器创建 template<typename Func> auto make_in_place_factory(Func f) { return InPlaceFactory<Func>(std::move(f)); } std::mutex create_mutex() { return std::mutex{}; } int main() { std::optional<std::mutex> m; // 用包装器传递工厂函数,在原位构造对象 m.emplace(make_in_place_factory(create_mutex)); }
内容来源于stack exchange

