C++中用void返回值签名删除拷贝/移动赋值运算符是否合法?
关于用void返回值删除赋值运算符的问题解答
好问题!咱们从编译器表现、标准要求、潜在坑点这几个维度来拆解:
1. 你的写法在GCC/Clang下是可行的
你给出的代码:
struct A { A() = default; void operator = (const A &) = delete; void operator = (A &) = delete; void operator = (A &&) = delete; };
确实能被GCC和Clang正常接受,并且达到禁止拷贝/移动赋值的效果——因为你显式删除了这些赋值运算符重载,编译器不会再生成默认的版本。
2. C++标准对删除赋值运算符的签名要求
首先明确:标准并没有强制要求赋值运算符的返回值必须是类的引用,这只是行业惯例(为了支持链式赋值,比如a = b = c)。
根据C++标准的定义:
- 拷贝赋值运算符是类的非模板成员函数
operator=,参数类型为X&、const X&、volatile X&或const volatile X&中的任意一种,返回值类型可以是任意的。 - 移动赋值运算符是类的非模板成员函数
operator=,参数类型为X&&,返回值类型同样无强制要求。
只要你声明了这类签名的operator=(哪怕返回void)并标记为delete,编译器就会认为你已经“用户声明”了赋值运算符,从而不会生成默认的版本。
不过这里有个关键细节:要确保你删除的签名覆盖了所有可能触发默认赋值的场景。比如:
- 如果你只删除
void operator=(A&),编译器依然会生成默认的A& operator=(const A&),此时用const左值赋值依然能通过编译,达不到禁止赋值的目的。 - 你的代码里删除了
const A&、A&、A&&三个版本,其实A&版本是冗余的——因为非常量左值可以隐式转换为const A&,删除const A&就已经覆盖了左值赋值的场景。但多删也不会有问题,只是没必要。
3. 潜在的干扰因素
要注意几个容易踩坑的点:
- 模板版
operator=不抑制默认生成:如果你写的是模板形式的赋值运算符(比如template<typename T> void operator=(const T&) = delete;),它不会阻止编译器生成默认的拷贝/移动赋值运算符,必须删除非模板的版本才行。 - 基类的赋值运算符:如果你的类继承了某个有赋值运算符的基类,默认情况下基类的赋值运算符不会被继承(C++11及以后需要用
using声明才会继承),但如果基类的运算符是public的,可能会通过基类对象的赋值影响派生类,需要额外注意。 - 链式赋值的编译错误:虽然你已经删除了赋值运算符,但如果有人不小心写了链式赋值(比如
a = b = c),因为你的operator=返回void,会直接触发编译错误——不过这本来就是你想要的禁止赋值的效果,所以不算问题。
内容的提问来源于stack exchange,提问作者Tomilov Anatoliy
相关产品推荐
相关产品推荐

