g++为何允许返回不可拷贝类?跨编译器编译差异疑问
问题拆解与解决方案
嘿,你碰到的是C++标准里关于隐式移动构造函数生成规则的跨编译器差异问题,咱们一步步理清楚:
核心矛盾:标准规则 vs 编译器实现
按C++11及之后的标准,当类里存在用户声明过的拷贝构造函数(哪怕是删除的),编译器就不会再给这个类隐式生成移动构造函数。你的基类删除了拷贝构造,所以派生类不仅继承了“不可拷贝”的特性,连隐式移动构造也被干掉了——理论上返回这个类的实例(需要拷贝或移动来完成返回操作)是完全非法的,那为啥两个编译器结果不一样?
为啥MSVC 2017报错?
MSVC 2017在这里严格遵守了标准:
- 基类的
= delete拷贝构造属于用户声明的构造函数,所以派生类的隐式移动构造被抑制。 - 当你尝试返回
Manager实例时,编译器需要调用拷贝或移动构造来完成返回,但这俩都不可用,自然就报错了(你看到的赋值运算符错误,本质是返回操作触发了拷贝赋值的检查,根源还是拷贝/移动构造都缺失)。
为啥MinGW-64 7.2能编译过?
这是GCC 7.2的一个非标准扩展:当返回的是局部对象(符合返回值优化RVO/NRVO的条件),GCC会跳过对拷贝/移动构造函数的检查,直接完成对象构造——这其实是把优化特性当成了语法上的例外,但这并不符合标准。你要是关掉优化(比如加编译参数-fno-elide-constructors),GCC 7.2同样会报错,因为此时必须验证拷贝/移动构造的可用性。
怎么写符合标准的代码?
如果你想保持类不可拷贝,但又能返回实例,就得显式给基类(和派生类,如果需要的话)定义移动构造和移动赋值:
// 不可拷贝但可移动的基类 class Base { public: // 禁用拷贝 Base(const Base&) = delete; Base& operator=(const Base&) = delete; // 显式默认移动构造和赋值 Base(Base&&) = default; Base& operator=(Base&&) = default; }; class Manager : public Base { public: // 派生类会自动继承基类的移动语义,也可以显式默认 Manager() = default; }; // 现在可以正常返回实例了 Manager createManager() { return Manager(); }
这样修改后,不管是MSVC还是GCC都会正常编译:我们显式提供了移动构造,返回时会调用它,同时保持了类不可拷贝的特性。
最后提个醒
别依赖编译器的非标准扩展写代码,这会让代码在不同环境下出问题。当你禁用拷贝语义时,如果需要支持对象转移(比如返回实例),一定要记得显式启用移动语义。
内容的提问来源于stack exchange,提问作者lorisleitner
相关产品推荐
相关产品推荐

