簡體   English   中英

為什么沒有基類指針或引用我們就不能在C ++中實現多態?

[英]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如果derivedbase ,的容器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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM