在引用存在的情况下赋值运算符和复制构造函数

10

我只是在用这段代码尝试引用:

class A
{
};

class B
{
public:
    B(A& a): m_a(a){}

    A& m_a;
};

int main()
{
    A a;
    B b(a);
    B b1 = b;
}
我本来期望这段代码 B b1 = b; 会产生一个错误,但当我在VS2008中编译时,只得到了一个警告信息:

warning C4512: 'B' : assignment operator could not be generated

我知道为什么会收到这个警告,但是难道编译器对于 B b1 = b; 不应该生成一个错误吗?这就像它生成了复制构造函数,但没有生成赋值运算符。这两者不是本质上相互关联的吗?如果一个操作无法生成,是否有意义仅为另一个操作生成默认实现呢?

你的代码没有使用赋值运算符,为什么会出现那个警告?顺便说一下,GCC 4.5.0 即使使用 -Wall -pedantic 也不会产生任何警告。 - Nawaz
MSVC很愚蠢,每次无法为类生成operator=时都会发出警告(因为该类包含引用或常量成员)。GCC则等到您实际尝试使用运算符时才会发出警告。 - Bo Persson
1
远非愚蠢之举,这鼓励你保持类的清洁。这就是警告而不是错误的用途。如果你没有收到警告,只是因为你没有执行赋值操作,那么你所做的就是将问题推迟到下一个可能想要执行该操作的人身上,而他将会更加不愿意和无能力修复你的代码。 - Daniel Saner
你正确的做法是要么自己实现一个赋值运算符,要么明确禁止赋值操作(不要忽略警告,如果你有这个想法的话 ;)). 这样,将来可能想使用你的代码的人就会知道情况。 - Daniel Saner
5个回答

11
warning C4512: 'B' : assignment operator could not be generated

问题 1: 为什么会出现这个警告?
当创建引用(References)时,只能进行一次初始化。在创建后,您不能将引用重新分配给另一个相同类型的变量,因为引用只是被创建的类型变量的别名,并将始终保持不变。尝试重新分配它将生成错误。
通常,编译器默认情况下会为每个类生成一个隐式位赋值运算符,但在这种情况下,由于class B具有一个成员作为引用m_a,如果编译器生成一个隐式赋值运算符,它将违反引用不可重新分配的基本规则。因此,编译器生成此警告以通知您无法生成隐式赋值运算符。

问题 2: 但是对B b1 = b;语句,不应该也生成编译器错误吗?
生成的警告和这个特定操作根本没有关系。
B b1 = b;调用隐式(正如@AndreyT所指出的那样)复制构造函数B::B(const B&)。 隐式复制构造函数是类生成的默认成员函数之一。因此,没有任何警告或错误。

问题 3: 就好像它生成了复制构造函数但没有生成赋值运算符。这两个是否本质上是相互关联的?
不是的,它们根本没有关系。是的,编译器生成了一个复制构造函数,但由于在构造函数体中成员引用m_a可以被初始化,因此它无法为其生成赋值运算符。 它只是在创建时进行初始赋值,而不是像=一样进行赋值。

问题 4: 当另一个无法生成时,仅为其中一个生成默认实现是否有意义?
问题3的答案似乎回答了这个问题。

只是为了重申代码示例中执行的操作:

B b(a);调用转换复制构造函数 B::B(A&)
B b1 = b;调用默认复制构造函数 B::B(const B&)

考虑其他情况。
如果您使用 B b1 = a;,它将调用B::B(A&),因此再次不会出现错误。

但是,如果B::B(A&)声明为explicit,编译器将标记错误,并且不会充当转换函数允许任何隐式转换

在此处查看相同内容的代码示例。


1
再次强调,在C++语言中,“default”一词具有特定的含义。不存在“默认复制构造函数”的概念。存在“默认构造函数”和“复制构造函数”。在这种情况下,复制构造函数由编译器隐式声明隐式定义 - AnT stands with Russia

4
在C++语言中,构造函数执行初始化操作,而赋值运算符执行赋值操作。初始化和赋值是两个完全不同的概念。
在C++语言中,引用可以被初始化,这就是为什么编译器可以为带有引用的类生成隐式复制构造函数的原因。语句“B b1 = b;”使用了隐式生成的复制构造函数。我不明白为什么你期望它会产生错误。
然而,引用本身不能被赋值(重新分配),这就是为什么编译器拒绝为带有引用的类生成隐式复制赋值运算符的原因。编译器通过发出警告来告知您这一点。如果您实际上尝试在程序中使用类B的赋值运算符,您将遇到一个错误。
在这方面,引用的情况与const成员的情况几乎相同:如果某个类具有const成员,则编译器将无法为该类生成隐式复制构造函数,但会拒绝生成隐式赋值运算符。

据我所知,在Op的示例中没有赋值操作,这两种情况都是复制初始化和直接初始化,基本上都会调用构造函数,我不明白赋值的参数从哪里来,请您详细说明一下? - Alok Save
@Als:你说得对,这里确实没有赋值操作。我从来没有说过有。这就是为什么编译器只发出一个简单的“警告”,纯粹是为了提供信息。如果程序尝试实际“使用”赋值操作,编译器会生成一个“错误”,而不是警告。 - AnT stands with Russia
编译器不会为具有引用的类生成隐式位赋值,但是该特定警告与OP问题中的默认初始化和复制初始化操作完全无关。 OP似乎将警告误认为是由于这些操作而产生的,并且OP的问题似乎暗示这两者相关,但实际上它们并不相关。 - Alok Save

1

引用只能被初始化一次,且不能被改变。构造函数有效是因为它初始化了 m_a,但复制操作将重新赋值给 m_a,这是不允许的。


c = b; /* 禁止 */ 不,它不是被禁止的。这只是意味着通过引用“c”将“b”的值赋给“a”的不同方式。 - Serge Dundich
你是对的。我的示例一点也不好。我会将其删除。 - neodelphi
我的例子一点也不好。实际上,我认为你的代码是一个很好的说明,为什么不能直接赋值引用。唯一错误的地方在于注释。 - Serge Dundich

1

你的示例不需要赋值运算符。VC++只是警告你,如果需要生成赋值运算符,则无法生成。如果你正在编写一个库并且忘记预料到库的用户可能需要复制B,那么这可能是有用的信息。

如果你不想看到这个警告,可以抑制它(例如,使用#pragma),或者声明一个私有赋值运算符并不实现它。如果有人尝试调用赋值运算符,它将在链接时失败。


0

B b(a); 是一个有效的语句。因为当你将类型为 A 的对象传递给 B 的构造函数时,会调用 B::B(A&)

顺便说一下,使用 g++ 编译器不会生成任何警告,就像你提到的那样。(而且在我看来,也不应该有任何警告,因为 B b1= b; 调用了 默认复制构造函数 B::B(const B&)。)


对不起,我的问题有误。我是说语句 B b1=b; - Asha
在C++语言中,“default”一词具有特定含义。并不存在“默认的复制构造函数”这种概念。实际上存在“默认构造函数”和“复制构造函数”两种情况。在这种情况下,复制构造函数会被编译器隐式声明隐式定义 - AnT stands with Russia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接