将一个派生类指针或引用赋给基类指针或引用是安全的且自动转换的;将一个派生类对象赋给基类对象将发生“切割”(sliced),是不正确的写法!
由于Son对象包含了一个Base对象,因此用一个Son对象去初始化Base对象时,实际上是将自身的Base类部分拷贝构造新的Base对象,而新的Base对象没有Son类成员的存储空间,因此将Son类的成员丢弃,即“切割”。此外,虽然新的Base对象已经copy了Son对象的Base这部分数据,但是虚表并没有更新(还是Son的虚表)。也就是说,这里有两个大隐患。
相反,将一个Son对象的指针赋给Base类的指针时,只是把Son对象的地址拷贝给一个指针类型的变量,并没有发生对象拷贝操作或者改变原来的对象。
看看下面的例子
void FunPtr(Base*);
void FunVal(Base );
//...
Base* basePtr = new Base();
Son * sonPtr = new Son();
//...
FunPtr( sonPtr); //No problem
FunPtr( basePtr); //NP
FunVal(*sonPtr); //wrong: sliced!!!
FunVal(*basePtr); //NP from the view of syntax, but has a performance problem
//for passing parameters by values,it will trigger a copy constructor
//...
Base base(*sonPtr); //Wrong: sliced!!!
Son son;
//...
*basePtr = son; //Wrong: sliced!!!
从上面的例子可知,如果在函数的参数列表中使用pass by value,要注意避免使用有继承关系的类;通常只有build in类型或者structure类型才不会有本文中的问题。
除了自己留意,避免这种错误外,有没有根本的解决之道呢?
如果一个类可以被继承,完全可以根据“按接口编程”的思想,提取一个接口;这在c++中就是纯虚基类。如果没必要纯虚,也可以把基类作为一个抽象类。因为抽象类不能实例化,那么上面的3处错误可以在编译期就被发现,而不是运行态:借用一句名言,能被编译器发现的问题不是问题!
扩展开来讲,具体类不适合作为基类,抽象类更合理。这个不仅仅有设计上的必要,也有实践上的意义(就是本文中所述的)
派生类与基类之间的赋值操作及其实质问题
本文深入探讨了派生类指针或引用赋给基类指针或引用的安全性,解释了将派生类对象赋给基类对象导致的‘切割’现象,同时提供了避免此类问题的方法,包括使用接口和抽象类。通过实例分析,展示了不同赋值方式的后果,强调了在参数传递和赋值操作中避免使用继承关系的类的重要性。

被折叠的 条评论
为什么被折叠?



