隐式定义的移动构造函数能否为非平凡构造函数?
CppReference相关定义
平凡移动构造函数
类T的移动构造函数是平凡的,当且仅当满足以下所有条件:
- 它不是用户提供的(即它是隐式定义或默认的);
- T没有虚成员函数,也没有虚基类;
- T的每个直接基类的选中移动构造函数都是平凡的;
- T的每个非静态类类型(或类类型数组)成员的选中移动构造函数都是平凡的。
隐式定义的移动构造函数
如果隐式声明的移动构造函数既未被删除也非平凡,那么当它被ODR使用或常量求值需要时,编译器会对其进行定义(即生成并编译函数体)。
疑问与解答
核心疑问
我理解“隐式声明的移动构造函数不能被删除”这一点,但“它必须非平凡”令我困惑。这是否意味着非平凡的移动构造函数应该是用户提供的?如果已经存在用户提供的构造函数,为何还需要隐式声明的构造函数生效?这里的逻辑是什么?隐式声明的移动构造函数必须非平凡的意义何在?
解答
首先得把几个概念的边界掰清楚:
- 隐式声明的移动构造函数:是编译器自动给类生成的声明,不用用户写代码,但它不一定会被定义(也就是生成实际的函数体),得满足特定条件才行。
- 平凡移动构造函数:本质上和
memcpy的效果完全等价——因为类里没有虚函数、没有需要特殊处理的成员,直接按字节拷贝就行,不需要额外逻辑。
那为什么隐式定义要求它非平凡?道理很直白:如果一个移动构造函数是平凡的,那根本没必要生成函数体。编译器完全可以直接用memcpy这类操作代替调用它,专门编译一个空的或者纯拷贝的函数纯属多余。只有当移动构造函数是非平凡的时候,才需要编译器生成具体的函数逻辑——比如要处理成员的移动、基类的移动,这些操作没法用简单的字节拷贝完成,必须有实际的函数体来执行这些步骤。
至于你问的“非平凡的移动构造函数是不是应该用户提供”,这是个误解。用户手写的移动构造函数确实是非平凡的,但隐式声明的也可以是非平凡的——举个例子,你的类里有个std::string成员,std::string的移动构造是非平凡的,那你的类的隐式移动构造函数也会是非平凡的,这时候编译器就会生成对应的函数体,去调用std::string的移动构造函数来处理这个成员。
再说说用户提供构造函数的情况:如果类已经有用户写的构造函数,隐式声明的移动构造函数会不会生效?这得看具体规则——比如你提供了拷贝构造但没写移动构造,C++11及以后编译器还是会隐式声明移动构造函数,但可能会被标记为删除。但如果你的类满足“未被删除且非平凡”的条件,那当这个隐式移动构造被ODR使用时,编译器还是会定义它——不过这种情况通常是你没显式声明移动构造,才会走隐式的路径。
总结一下:要求隐式定义的移动构造函数非平凡,核心原因就是平凡的移动构造完全不需要生成函数体,直接字节拷贝就能搞定;只有非平凡的才需要编译器生成实际逻辑,处理那些不能直接拷贝的成员或基类。
内容的提问来源于stack exchange,提问作者Rango

