[英]Why we can't implement polymorphism in C++ without base class pointer or reference?
首先看下面的代碼(在此代碼中,形狀是基類,而線是派生類)
void drawshapes(shape sarray[],int size)
{
for(int i=0;i< size; i++)
sarray[i].draw();
}
main()
{
line larray[10];
larray[0]=line(p1,p2);//assuming that we have a point class
larray[1]=line(p2,p3);
..........
drawshapes(larray,10);
}
當我們編譯該程序時,將首先調用shape的draw方法,然后程序終止。 為什么終止? 為什么沒有基類指針或引用就不能實現多態性的技術原因是什么? 如果我們嘗試使用對象數組實現多態,編譯器將做什么? 請通過示例以易於理解的方式進行解釋。 我將非常感謝。
首先:您要混合兩個概念:多態性和值與參考語義。
多態性有多種形式。 根據您使用的運行時,其他選項可用。
一種解釋型語言(例如Ruby,Python,Javascript等)允許進行“鴨子類型”操作:如果對象僅具有一個稱為foo
的方法,則可以調用它。 通常,這些語言會進行垃圾回收,因此指針與對象的概念不太相關。
C ++有不同的觀點:允許多態,但是以更嚴格的方式。 強制使用一個通用的基類(可能是抽象的)可以使編譯器檢查您的代碼的語義:這樣,編譯器可以確保您確實是實現了預期接口的foo
方法,而不是foo
的雜亂混搭。
這種多態性是通過使用virtual
函數實現的: virtual
函數的指針在實現之間可能有所不同。 foo
的調用者首先必須查找函數指針的值,然后跳轉到該函數。
到目前為止為多態。
現在進行遏制:如果在C ++中創建line
對象數組,則這些對象在內存中彼此相鄰; 它們被價值所包含。 當您將數組傳遞給函數時,被調用的函數只能接收相同類型的數組。 否則,在數組中采用sizeof(shape)
的步驟,我們最終將在line
的中間。
為了解決這個問題,您可以“按引用”包含對象-在C ++中,我們為此使用了指針。
但是還有另一種實現多態功能的方法:模板。 您可以使用模板參數編寫drawshapes
函數,該參數說明您使用的對象類型:
template< typename tShape, size_t N >
void drawshapes( tShape (&aShapes)[N] ) {
for( tShape* shape=aShapes; shape != aShapes+N; ++shape ) {
shape->draw();
}
}
(注意:有stl函數可以簡化此操作,但這超出了問題的范圍。
std::for_each( shapes, shapes+10, mem_fun_ref( &shape::draw ) );
)
您在問一個問題,並提供了一個失敗的代碼示例,但原因有所不同。 從您的問題的措辭:
為什么多態性需要引用/指針?
struct base {
virtual void f();
};
struct derived : public base {
virtual void f();
};
void call1( base b ) {
b.f(); // base::f
}
void call2( base &b ) {
b.f(); // derived::f
}
int main() {
derived d;
call1(d);
call2(d);
}
當您使用按值傳遞語義(或將派生的元素存儲在基本容器中)時,您將創建derived
類型的元素的base
類型的副本。 這稱為切片 ,因為它類似於以下事實:您有一個derived
對象,並且僅從該對象中切片/切割了base
子對象。 在示例中, call1
在main中的對象d
不起作用,而是使用base
類型的臨時類型,並調用了base::f
。
在call2
方法中,您正在傳遞對base
對象的引用。 當編譯器在main中看到call2(d)
,它將在d
創建對base
子對象的引用,並將其傳遞給函數。 該函數在類型為base
的引用上執行該操作,該引用指向derived
類型的對象,並將調用derived::f
。 同樣的情況發生在指針上,當你得到一個base *
為derived
對象,該對象是否仍然derived
。
為什么我不能將derived
指針的容器傳遞給采用base
指針的容器的函數?
_Clearly如果derived
是base
,的容器derived
是的容器base
。
不。 derived
容器不是base
容器。 那會破壞類型系統。 使用的容器的最簡單的例子derived
作為容器base
斷裂的類型系統對象如下。
void f( std::vector<base*> & v )
{
v.push_back( new base );
v.push_back( new another_derived );
}
int main() {
std::vector<derived*> v;
f( v ); // error!!!
}
如果語言允許使用標記為錯誤的行,那么它將允許應用程序將非derived*
類型的元素插入容器,這將帶來很多麻煩...
但是問題是關於值類型的容器...
當您具有值類型的容器時,元素將被復制到容器中。 將derived
類型的元素插入到base
類型的容器中,將在derived
對象內復制base
類型的子對象。 與上面的切片相同。 除了語言限制之外,還有一個很好的理由:當您擁有一個base
對象容器時,您就有空間僅容納base
元素。 您不能將較大的對象存儲到同一容器中。 否則,編譯器甚至不知道為每個元素保留多少空間(如果以后再使用更大的類型擴展該怎么辦?)。
在其他語言中,這似乎實際上是允許的(Java),但事實並非如此。 唯一的變化是語法。 當您在Java中使用String array[]
,實際上是在編寫C ++中的string *array[]
。 所有非原始類型都是該語言中的引用,並且您沒有在語法中添加*
的事實並不意味着容器包含String的實例 ,容器將引用包含在String中,與c ++指針比c ++更為相關參考。
您不能傳遞線陣列而不是形狀陣列。 您必須使用指針數組。 發生這種情況的原因是,當函數嘗試訪問第二個成員時,它執行*(sarray + sizeof(shape))而不是*(sarray + sizeof(line)),這是訪問數組第二個元素的正確方法行。
您想要這樣的東西:
void drawshapes(shape *sarray[],int size)
{
for(int i=0;i< size; i++)
sarray[i]->draw();
}
main()
{
shape *larray[10];
larray[0] = new line(p1,p2);//assuming that we have a point class
larray[1] = new line(p2,p3);
..........
drawshapes(larray, 10);
// clean-up memory
...
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.