MSVC C++23下如何让编译器对不可移动类使用拷贝而非移动
问题:创建仅允许拷贝、禁用移动的C++类,解决拷贝初始化右值的编译错误
我正在使用MSVC编译器与C++23标准,需要创建一个仅允许拷贝、不允许移动的类。现有代码如下:
#include <cstring> struct MyClass { MyClass() = default; //copy MyClass(const MyClass& other) { data = other.data; } MyClass& operator=(const MyClass& other) { data = other.data; return *this; } //cannot allow move for this class MyClass(MyClass&& other) = delete; MyClass& operator=(MyClass&& other) = delete; int data = 0; }; int main() { MyClass m = MyClass{}; //tries to use move //works MyClass newm{}; m = newm; }
当执行MyClass m = MyClass{};时,编译器尝试调用已删除的移动构造函数,触发编译错误,报错信息如下:
error C2280: 'MyClass::MyClass(MyClass &&)': attempting to reference a deleted function message : see declaration of 'MyClass::MyClass' message : 'MyClass::MyClass(MyClass &&)': function was explicitly deleted
需求:能否修改类定义,让编译器在移动构造不可用时自动使用拷贝构造,使MyClass m = MyClass{};正常运行?
解决方案
方案一:移除显式删除的移动操作(推荐)
不需要显式删除移动构造/赋值运算符,只需保留显式定义的拷贝操作,编译器会自动禁用移动操作,同时在右值初始化场景下回退到拷贝构造。
修改后的代码:
#include <cstring> struct MyClass { MyClass() = default; // 显式定义拷贝构造与拷贝赋值 MyClass(const MyClass& other) { data = other.data; } MyClass& operator=(const MyClass& other) { data = other.data; return *this; } // 移除移动操作的删除声明,编译器不会自动生成它们 int data = 0; }; int main() { MyClass m = MyClass{}; // 调用拷贝构造,正常编译 MyClass newm{}; m = newm; // 正常调用拷贝赋值 }
原理说明
- C++标准规定:当类显式声明了拷贝构造/赋值时,编译器不会自动生成移动构造/赋值运算符,相当于移动操作被隐式禁用。
- 对于
MyClass m = MyClass{};这种拷贝初始化场景,由于const MyClass&可以绑定到右值,当移动构造不存在时,编译器会直接匹配拷贝构造函数执行。 - 之前显式删除移动构造的问题在于:删除操作会让该函数“存在但不可用”,编译器会优先尝试匹配右值对应的
MyClass&&参数,发现函数被删除后直接报错,不会回退到拷贝构造。
方案二:保留删除声明的兼容写法(可选)
如果需要明确对外表明禁用移动(保留删除声明),可以通过重载接受右值的拷贝构造/赋值函数,覆盖被删除的移动操作匹配逻辑:
#include <cstring> struct MyClass { MyClass() = default; // 常规左值拷贝构造 MyClass(const MyClass& other) { data = other.data; } // 新增:接受右值的拷贝构造,优先匹配右值初始化场景 MyClass(MyClass&& other) : MyClass(static_cast<const MyClass&>(other)) {} // 常规左值拷贝赋值 MyClass& operator=(const MyClass& other) { data = other.data; return *this; } // 新增:接受右值的拷贝赋值 MyClass& operator=(MyClass&& other) { return *this = static_cast<const MyClass&>(other); } // 可保留删除声明,也可以不写(上面的重载已覆盖右值场景) // MyClass(MyClass&& other) = delete; // MyClass& operator=(MyClass&& other) = delete; int data = 0; };
这种写法通过重载MyClass&&参数的构造/赋值函数,内部调用拷贝逻辑,让右值初始化时优先匹配该重载,避免触发被删除的移动操作报错。
内容的提问来源于stack exchange,提问作者Big KP
相关产品推荐
相关产品推荐

