从派生类到基类的转换在继承表中是往上走的,因此叫upcasting,其总是安全的,因为其将一个特定的对象转换为了一个更通用的对象。唯一的变化是其可能失去某些成员,但它至少还是个instrument,因此编译器不用显式的强制转换。
当编译器为派生类自动生成拷贝构造函数时,其将自动调用基类的拷贝构造函数,然后是各个成员对象的拷贝构造函数。
//: C14:CopyConstructor.cpp
// Correctly creating the copy-constructor
#include
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; // Calls copy-constructor
cout << "values in c2:/n" << c2;
} ///:~
return os << (Parent&)c << c.m
Parent(int ii)
Member(int ii)
Child(int ii)
calling copy-constructor:
Parent(const Parent&)
Member(const Member&)
values in c2:
Parent: 2
Member: 2
Child: 2
当自己实现拷贝构造函数时,忘记了调用基类的拷贝构造函数,这时候编译器会调用默认的构造函数,因为对于C++来说必须确保任何对象初始化过。
Child(const Child& c) : i(c.i), m(c.m) {}
Parent(int ii)
Member(int ii)
Child(int ii)
calling copy-constructor:
Parent() //默认的无参构造函数
Member(const Member&)
values in c2:
Parent: 0
Member: 2
Child: 2
因此当自己实现拷贝构造函数时必须确保调用了基类的拷贝构造函数。如下:
Child(const Child& c)
: Parent(c), i(c.i), m(c.m) {
cout << "Child(Child&)/n";
}
这又是一个upcasting的例子,将child对象作为基类的引用参数传递给基类,因为child的引用将自动转换为parent的引用。
除了在函数调用时编译器可以实现自动转换外,在进行指针或者引用赋值时也可以自动转换。和函数调用一样,这两种情况都不需要显式的强制转换。
Wind w;
Instrument* ip = &w; // Upcast
Instrument& ir = w; // Upcast
在upcast的时候会失去类型信息,如下编译器只能将ip作为Instrument的指针来调用
Wind w;
Instrument* ip = &w;
ip->play(middleC);
此时调用的是基类的Instrument::play( )而非本意Wind::play( ).,这种问题可以通过面向对象的编程技术的第三个里程碑多态polymorphism来解决,在C++中是通过虚函数来实现的。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
采用多态技术是值传递和地址传递有很大的区别,因为地址大小总是一样的;传递派生类的地址和基类的地址是一样的,因为派生类中包含了基类。值传递时,派生对象将剥离只剩下基类部分。
//: C15:ObjectSlicing.cpp #include #include using namespace std; class Pet { string pname; public: Pet(const string& name) : pname(name) {} virtual string name() const { return pname; } virtual string description() const { return "This is " + pname; } }; class Dog : public Pet { string favoriteActivity; public: Dog(const string& name, const string& activity) : Pet(name), favoriteActivity(activity) {} string description() const { return Pet::name() + " likes to " + favoriteActivity; } }; void describe(Pet p) { // Slices the object cout << p.description() << endl; } int main() { Pet p("Alfred"); Dog d("Fluffy", "sleep"); describe(p); describe(d); } ///:~
|
This is Alfred This is Fluffy
|
两个原因:
首先,当调用describe时,只有Pet大小的东西在栈上分配释放,因此多余的部分将被剥离;
其次,因为是值传递,编译器知道具体的数据类型,将调用pet的拷贝构造函数,其将VPTR初始化为PET的VTABLE,然后将dog的基类部分拷贝至p中,因此p将变成完完全全的pet对象。
向上转换为基类对象是很少见的,应该尽量避免,若将description声明为纯虚函数,则编译器将禁止基类对象的值传递,因为抽象类不能够创建对象。这是纯虚函数的一个重要特性,其可以防止对象剥离。