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 编译时,我只会收到警告

警告 C4512:“B”:无法生成赋值运算符

我明白为什么我会收到此警告。但是编译器不应该也为语句生成错误B b1 = b;吗?就像它生成了复制构造函数但没有生成赋值运算符。这两者不是天生就相互联系的吗?当无法生成另一个时,只为其中一个生成默认实现是否有意义?

4

5 回答 5

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

问题1:为什么会出现这个警告?
引用只能在创建时初始化一次。您不能在创建后重新分配对另一个相同类型变量的引用,因为 Reference 只是为其创建它的类型变量的别名,并将继续如此。尝试重新分配它会产生错误。
通常,编译器默认为每个类生成一个隐式按位赋值运算符,但在这种情况下,由于class B有一个引用作为成员m_a,如果编译器要生成一个隐式赋值运算符,它将打破不能重新分配引用的基本规则。因此编译器会生成此警告以通知您它无法生成隐式赋值运算符。

问题 2:但是编译器不应该为 B b1 = b; 生成错误吗?也声明?
生成的警告和这个特定的操作完全没有关系。
B b1 = b;调用隐式(正如@AndreyT 正确指出的)复制构造函数B::B(const B&)。隐式复制构造函数是类默认生成的成员函数之一。所以没有警告或错误。

问题3:就像它生成了复制构造函数但没有生成赋值运算符。这两者不是天生就相互联系的吗?
不,它们根本没有关系。是的,编译器生成了一个复制构造函数,但由于上述问题 1 的答案中指定的原因,它无法生成赋值运算符。这是因为成员引用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并且不允许任何implicit conversions作为conversion function.

在这里检查相同。

于 2011-05-25T06:18:50.147 回答
4

C++ 语言中的构造函数执行初始化,而赋值运算符执行赋值。初始化和赋值是两个完全不同的概念。

C++ 语言中的引用可以被初始化,这就是编译器为具有引用的类生成隐式复制构造函数没有问题的原因。该B b1 = b;语句使用隐式生成的复制构造函数。我不明白您为什么期望它会产生错误。

但是,引用本身不能被赋值(重新赋值),这就是编译器拒绝为具有引用的类生成隐式复制赋值运算符的原因。编译器通过发出警告通知您。如果您实际上尝试B在程序中对类使用赋值运算符,您最终会遇到错误。

在这方面,引用的情况与const成员的情况几乎相同:如果某个类有const成员,编译器为这个类生成隐式复制构造函数没有问题,但会拒绝生成隐式赋值。

于 2011-05-25T16:52:39.573 回答
1

引用只能初始化一次,不能更改。构造函数是有效的,因为它初始化了 m_a,但副本会重新分配 m_a,这是被禁止的。

于 2011-05-25T06:13:42.050 回答
1

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

如果您不想要警告,您可以取消它(例如,使用#pragma),或者您可以声明一个私有赋值运算符而不实现它。如果有人随后尝试调用赋值运算符,它将因链接时错误而失败。

于 2011-05-25T17:29:23.313 回答
0

B b(a);是一个有效的陈述。因为B::B(A&)当您将类型的对象传递AB.

顺便说一句,g++ 没有像你提到的那样产生警告。(恕我直言,不应该有任何警告,因为B b1= b;调用了默认的复制构造函数 B::B(const B&)。)

于 2011-05-25T06:13:21.770 回答