【C++學習筆記】父類指標指向子類物件
虛擬函式的作用主要是實現了多型的機制。簡而言之就是用父型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。但僅僅可呼叫父類含有的函式,非父類函式不能呼叫。
普通虛擬函式呼叫
假設我們有下面的類層次:
#include <iostream>
using namespace std;
class A
{
public:
A(){};
~A(){}
virtual void foo()
{
cout << "A::foo() is called" << endl;
}
};
class B :public A
{
public:
B(){}
~B(){}
void foo()
{
cout << "B::foo() is called" << endl;
}
void fun()
{
cout << "B::fun() is called" << endl;
}
virtual void fun1()
{
cout << "B::fun() is called" << endl;
}
};
int main(void)
{
A *a = new B();
a->foo(); // 在這裡,a雖然是指向A的指標,但是被呼叫的函式(foo)卻是B的!
// a->fun(); // 這裡呼叫錯誤,提示:error C2039: “fun”: 不是“A”的成員, e:\debug\base\test\main.cpp(8) : 參見“A”的宣告
// a->fun1(); // 這裡呼叫錯誤,提示:error C2039: “fun1”: 不是“A”的成員, e:\debug\base\test\main.cpp(8) : 參見“A”的宣告
return 0;
}
以上例子說明,子類的虛擬函式替換了父類的同名虛擬函式(參見【C++學習筆記】虛擬函式實現多型原理)實現通過父類呼叫子類函式實現的功能。非父類含有的函式無法呼叫,可能因為父類中未包含子類中相關函式資訊。
建構函式與解構函式呼叫
假設有如下類層次:
class A
{
public:
A(){ cout << "A::A() is called" << endl; };
~A(){ cout << "A::~A() is called" << endl; }
virtual void foo()
{
cout << "A::foo() is called" << endl;
}
};
class B :public A
{
public:
B(){ cout << "B::B() is called" << endl; }
~B(){ cout << "B::~B() is called" << endl; }
void foo()
{
cout << "B::foo() is called" << endl;
}
};
int main(void)
{
A *a = new B();
a->foo(); // 在這裡,a雖然是指向A的指標,但是被呼叫的函式(foo)卻是B的!
delete a;
return 0;
}
程式輸出:
A::A() is called
B::B() is called
B::foo() is called
A::~A() is called
由程式輸出可以看出,程式先構造父類,再構造子類,析構時未呼叫子類的解構函式。(這是否可以理解為:父類中未包含子類的資訊,所以沒有被呼叫)。若子類的建構函式存在動態記憶體分配,則會存在記憶體洩漏的問題。若父類解構函式為虛擬函式,則程式輸出如下:
A::A() is called
B::B() is called
B::foo() is called
B::~B() is called
A::~A() is called
子類的動態記憶體分配就可以被釋放。
雖然呼叫delete釋放父類指標a,但是子類B的解構函式也被呼叫了。為何父類A解構函式為虛擬函式,釋放父類指標可以呼叫子類解構函式,父類A解構函式不為虛擬函式,釋放父類指標不呼叫子類解構函式?父類中也沒有包含子類解構函式的資訊呀!!!!!!!!!
父子類指標強制轉換的安全性
假設有如使用:
class A
{
public:
A(){ cout << "A::A() is called" << endl; };
virtual~A(){ cout << "A::~A() is called" << endl; }
virtual void foo()
{
cout << "A::foo() is called" << endl;
}
};
class B :public A
{
public:
B()
{
cout << "B::B() is called" << endl;
}
virtual~B()
{
cout << "B::~B() is called" << endl;
delete [] pt;
}
void foo()
{
cout << "B::foo() is called" << endl;
}
void fun()
{
cout << "B::fun() is called" << endl;
}
};
int main(void)
{
A *a = new A();
B *b = static_cast<B*>(a); // 需顯示強制轉換
b->fun();
delete b;
return 0;
}
程式輸出:
A::A() is called
B::fun() is called
A::~A() is called
父類指標強制轉換為子類指標,並呼叫了子類函式,釋放時釋放的是父類A指標,並未釋放B指標。父類指標a強制轉換過程中並未呼叫子類B建構函式,釋放時也未呼叫子類B解構函式。原父類動態分配的記憶體強制轉換為子類,向下強制轉換為子類存在較大風險,如下所示:
class A
{
public:
A(){ cout << "A::A() is called" << endl; };
virtual~A(){ cout << "A::~A() is called" << endl; }
virtual void foo()
{
cout << "A::foo() is called" << endl;
}
};
class B :public A
{
public:
B()
{
cout << "B::B() is called" << endl;
pt = new int[10];
for (int i = 0; i < 10; i++)
pt[i] = i;
}
virtual~B()
{
cout << "B::~B() is called" << endl;
delete [] pt;
}
void foo()
{
cout << "B::foo() is called" << endl;
}
void fun()
{
cout << "B::fun() is called" << endl;
cout << pt[0] << endl;
}
int *pt;
};
int main(void)
{
A *a = new A();
B *b = static_cast<B*>(a);// 需顯示強制轉換
b->fun();
delete a;
return 0;
}
程式輸出異常,因為子類B呼叫的函式使用了需要在子類建構函式中動態分配的記憶體。因為強制轉換過程中,未呼叫子類B的建構函式,pt記憶體沒有進行動態分配。
int main(void)
{
A *a = new B();
B *b = static_cast<B*>(a);
b->fun();
delete a;
return 0;
}
程式輸出:
A::A() is called
B::B() is called
B::fun() is called
0
B::~B() is called
A::~A() is called
父類指標強制轉換為子類指標,並呼叫了子類函式(存在使用動態記憶體分配部分)。此情況說明:父類指標a指向子類指標,再將父類指標轉換為子類B使用的安全的。