std::optional的emplace操作机制及三种调用方式差异探究
std::optional::emplace的析构调用与操作类型解析
问题背景
在使用std::optional的emplace函数原地创建对象时,发现类的析构函数被调用,想明确该函数对类对象执行的是拷贝还是移动操作;同时三种不同的emplace调用方式中,两种会触发析构,希望了解三者的差异。
基础类代码
class myClass{ private: int m_num; public: myClass(int num) : m_num(num){} ~myClass(){ std::cout << "myClass destructed\n"; } };
主函数代码
int main(){ std::optional<myClass> a; std::optional<myClass> b; std::optional<myClass> c; std::cout << "emplacing a: \n"; a.emplace(myClass{0}); std::cout << "emplacing b: \n"; b.emplace<myClass>(0); std::cout << "emplacing c: \n"; c.emplace(0); std::cout << "done\n"; while (true) {}; }
程序输出
emplacing a: myClass destructed emplacing b: myClass destructed emplacing c: done
解答
1. emplace的操作类型:拷贝还是移动?
std::optional::emplace的核心是原地构造对象,具体行为由传入的参数决定:
- 若传入临时对象(如
myClass{0}),emplace会优先调用移动构造函数,在std::optional的内部存储空间中构造对象;若类未定义移动构造函数,则会调用默认生成的拷贝构造函数。临时对象完成构造任务后生命周期结束,触发析构。 - 若直接传入类构造所需的原始参数(如
0),emplace会直接用这些参数在std::optional内部原地构造对象,全程无额外的拷贝/移动操作,自然不会触发额外的析构。
2. 三种调用方式的差异
方式一:a.emplace(myClass{0});
先显式创建一个临时myClass对象myClass{0},再将该临时对象作为参数传给emplace。emplace借助这个临时对象,通过移动/拷贝构造完成std::optional内部对象的创建。临时对象随后销毁,因此打印析构日志。
方式二:b.emplace<myClass>(0);
显式指定了emplace的模板参数myClass,此时函数会先以0为参数隐式构造一个临时myClass对象,再通过移动/拷贝构造完成std::optional内部对象的创建。临时对象销毁后触发析构。这种写法属于冗余操作——std::optional已经明确存储类型为myClass,显式指定模板参数反而会生成额外的临时对象。
方式三:c.emplace(0);
这是emplace的标准用法:直接传入myClass构造所需的参数0,emplace直接在std::optional的内部存储空间调用myClass(0)构造对象,全程无额外临时对象生成,因此不会触发额外的析构。
内容的提问来源于stack exchange,提问作者green blanket
相关产品推荐
相关产品推荐

