我只是使用此代码试验参考文献:
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;
语句生成错误?它就像它生成了复制构造函数但没有生成赋值运算符。这两者本身并不相互联系吗?当另一个无法生成时,为其中一个生成默认实现是否有意义?
答案 0 :(得分: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
则不允许编译错误。
检查相同的here。
答案 1 :(得分:4)
C ++语言中的构造函数执行初始化,而赋值运算符执行赋值。初始化和分配是完全不同的概念。
可以初始化C ++语言中的引用,这就是为什么编译器为带引用的类生成隐式复制构造函数没有问题。 B b1 = b;
语句使用隐式生成的复制构造函数。我不明白为什么你期望它产生错误。
但是,无法分配(重新分配)引用本身,这就是编译器拒绝为具有引用的类生成隐式复制赋值运算符的原因。编译器通过发出警告通知您。如果您实际尝试在程序中使用类B
的赋值运算符,则最终会出错。
在这方面,引用的情况与const
成员的情况几乎相同:如果某个类具有const
成员,编译器将没有问题为此类生成隐式复制构造函数,但会拒绝生成隐式赋值。
答案 2 :(得分:1)
引用只能初始化一次,不能更改。构造函数是有效的,因为它初始化m_a,但副本将重新分配m_a,这是禁止的。
答案 3 :(得分:1)
您的样本不需要赋值运算符。 VC ++只是警告你,如果需要,它将无法生成一个。如果您正在编写库而您忘记预计库的用户可能需要复制B,那么这可能是有用的信息。
如果您不想要警告,可以禁止它(例如,使用#pragma),或者您可以声明私有赋值运算符而不实现它。如果有人试图调用赋值运算符,它将失败并出现链接时错误。
答案 4 :(得分:0)
B b(a);
是一个有效的声明。因为将B::B(A&)
类型的对象传递给A
的构造函数时会调用B
。
顺便说一句,使用g ++时,没有像你提到的那样生成警告。 (恕我直言,不应该有任何警告,因为B b1= b;
调用了默认复制构造函数 B::B(const B&)
。)