这个例子取自Bruce Eckel的"C++中的思考"第14章"Upcasting and the Copy Constructor"一节。
#include <iostream>
using namespace std;
class Parent
{
int i;
public:
Parent(int ii) : i(ii) { cout << "Parent(int ii)n"; }
Parent(const Parent& b) : i(b.i) { cout << "Parent(const Parent&)n"; }
Parent() : i(0) { cout << "Parent()n"; }
friend ostream& operator<<(ostream& os, const Parent& b)
{ return os << "Parent: " << b.i << endl; }
};
class Member
{
int i;
public:
Member(int ii) : i(ii) { cout << "Member(int ii)n"; }
Member(const Member& m) : i(m.i) { cout << "Member(const Member&)n"; }
friend ostream& operator<<(ostream& os, const Member& m)
{ return os << "Member: " << m.i << endl; }
};
class Child : public Parent
{
int i;
Member m;
public:
Child(int ii) : Parent(ii), i(ii), m(ii) { cout << "Child(int ii)n"; }
friend ostream& operator<<(ostream& os, const Child& c)
{ return os << (Parent&)c << c.m << "Child: " << c.i << endl; }
};
int main() {
Child c(2);
cout << "calling copy-constructor: " << endl;
Child c2 = c;
cout << "values in c2:n" << c2;
}
作者对此代码作出以下评论:
"Child的运算符<<很有趣,因为它呼叫运营商<lt;对于其中的父零件:通过铸造父对象的子对象&(如果改为转换为基类对象你通常会得到不理想的结果):
return os << (Parent&)c << c.m << "Child: " << c.i << endl;
我还运行了这个程序,将上面的指令替换为:
return os << (Parent)c << c.m << "Child: " << c.i << endl;
推进器运行时没有问题,只有一个预期的差异。现在再次调用Parent
复制构造函数,将参数c
复制到Parent::operator<<()
。
那么,作者所说的不良结果是什么呢?
问题是,当您将Child硬强制转换为Parent(而不是Parent&)时,您只会将使Child成为Child的所有内容都分割掉。
通常,当您的类具有虚拟函数(通常在类层次结构内)时,您可以并且将(取决于内部布局、继承类的数量等)修改vptr,然后一直向下进入未定义行为的领域。也就是说,在类层次结构中不使用引用(或指针)有效地扼杀了所有神奇的继承机制(也称为多态性)。
这有点像在说dog=平面;-通过使用重新解释强制转换(这就是C风格的强制转换),你可以利用编译器发出的任何警告,因为你是在告诉它闭嘴。
一点切线。。。
经验法则:基类不应该是可复制的,而应该是可克隆的。
实现:禁用Copy Constructor和Copy Assignment Operator,或者(简单地)创建一个纯虚拟方法。
松弛:在没有纯虚拟方法的情况下,使基类复制构造函数和赋值运算符protected
更容易警告:这意味着子类现在可以调用其父类的副本,这可能会触发切片问题。
注意:对于C++11,这也适用于移动对应项。