2012-05-06 38 views
1

我:使用類作爲BaseClass的

class BASE{ 
public: 
    virtual void func1() = 0; 
}; 

然後,我有一些派生類,如:

class Derived1 : public BASE{ 
public: 
    void func1() {...implementation....} 
}; 

class Derived2 : public BASE{ 
    public: 
     void func1() {...implementation....} 
    }; 

在主,我想這樣做(僞代碼):

if (cond){ 
    BASE b := new Derived1 
} 
else{ 
    BASE b := new Derived2 
} 
b.func1(); 

因此,調用的func1()函數是專門用於派生類的函數。

我試圖做的:

BASE b = new Derived1(); 

但是編譯器會抱怨。

在C++中可以這樣做嗎?怎麼樣?

+0

你可能想讓'Derived1'&2實際上是從某個東西派生出來的。 – Mat

+0

爲什麼你的函數沒有返回類型? – bitmask

+2

如果你要問這裏,你應該總是解釋編譯器如何抱怨。 (你也應該嘗試在谷歌中輸入錯誤,它比人類快) – Shep

回答

2

有兩種方法可以做到這一點:如果你想創建

Base *b = new Derived1;

  • 使用類型基類的常引用:

    • 使用類的基類指針衍生於複製結構:

      Base const& b = Derived;

    • 使用類型基類的非const引用如果你想要分配在其他地方創建一個派生類對象:

      Derived d; Base& b = d;

    尼特:

    • 爲了要繼承,您需要在類定義中指定:

      class Derived1 : public Base {

    • 您的函數需要返回值。

      class B{ public: virtual void func() = 0; };

  • +0

    哪個更好?指針或參考? – Aslan986

    +3

    沒有一個正確答案。這取決於你的應用程序。如果可以,請使用參考,並僅在必要時使用指針。如果你是C++的新手,我建議你開始熟悉引用! – dirkgently

    +1

    -1。你說的是正確的,但是解釋如何用普通指針來使用'new',而不必提及'delete'就完全是危險的。 – leftaroundabout

    2

    你正試圖分配一個指向普通對象的指針。

    new Derived1() 
    

    該表達式返回一個指針。您需要製作BASE指針(BASE *),或者只是在堆棧上創建它(最好是Base b;)。不過,在這種情況下,你需要指針(或引用)來在類之間進行轉換。

    此外,您不能在if-else語句內創建對象,因爲它稍後會在超出範圍時使用。

    這裏的東西,你可以這樣做:

    BASE *b; 
    
    if (cond) 
        b = new Derived1(); 
    else 
        b = new Derived2(); 
    
    b->func1(); 
    

    當然你要記得事後delete您的指針。考慮簡單地使用智能指針。

    1

    你忘了這個嗎?

    class Derived1 : public BASE 
    { 
    }; 
    

    至少我沒有看到它在你的代碼 - 衍生而來

    +0

    對不起,我編輯。謝謝。 – Aslan986

    4

    使用指針(或引用)的基類,否則你的對象只是將被複制到一個普通的基本實例:

    BASE* b = new Derived1; 
    b->func1(); 
    
    // later... 
    delete b; 
    

    此外,不要忘記讓你的析構函數虛擬爲這種情況。

    由於整個新/刪除的東西是有點麻煩,只是爲了獲得多態性的工作,人們往往時下使用智能指針:

    std::shared_ptr<BASE> b=std::make_shared<Derived1>(); 
    b->func1(); 
    
    +1

    這是要走的路,但你忘了告訴虛擬析構函數。 – leftaroundabout

    +0

    噢,儘管虛擬析構函數僅用於第一種情況。如果shared_ptr首先構建在實際類型上,它實際上會「記錄」析構函數。 – ltjax

    0

    如果函數簽名都是一樣的,也許是一個接口聲明在這種情況下會更合適...然後讓每個類實現接口,並聲明該變量爲您聲明的接口類型...

    3

    顯然,你已經習慣了垃圾收集OO語言這樣一個Java。爲了很好地回答這個問題,從這個角度來看可能是很好的做法。

    當你寫的東西

    Base b = new Derived1(); 
    

    在Java中,會發生以下情況:

    1. 指針 「&b」(我將它β調用)到通用Base對象的分配堆棧
    2. 一個新的Derived1對象(我將它稱爲d)分配在堆上
    3. β設置爲指向Derived對象。

    你在Java中容易擺脫這個的原因是有一個垃圾回收器。只要β在堆棧上並指向d,這將不會起作用,因爲GC知道d仍然可以訪問並且可能正在使用。但只要沒有指針指向d(例如因爲您聲明b的函數離開了作用域),GC就被允許釋放由d佔用的空間。很容易,但垃圾收集有several disadvantages,我們不希望在C++中。

    所以在C++中,如果你這樣做

    Base* b = new Derived1(); 
    

    其直接對應於Java版本,你有一個問題:當b離開的範圍,沒有什麼是指d了,但它還是謊言在堆上!你需要自己刪除

    delete b; 
    

    (注意,這有很大的優勢,你可以準確地確定指向它刪除在其中,而垃圾收集器可以把它躺在身邊白白相當長的時間,並刪除它只有當你開始用完內存時)。但是再一次,這還不夠:不像垃圾收集器,你的程序不會自動知道b指向一個Derived1對象而不是Base對象,因此delete將被刪除,認爲它正在處理一個Base。但它很容易碰到這個得到:包括在Base類虛析構函數,像

    class Base{ 
    public: 
        virtual void func1() = 0; 
        virtual ~Base() {} 
    }; 
    


    現在,這個需要手工刪除一切顯然是有點危險的:如果你忘記做它,你有一個內存泄漏,即對象是從來沒有只要你的程序運行從堆中刪除。因此,應該使用智能指針而不是簡單的C型指針,這會自動刪除它們在離開作用域時指向的對象。在C++ 11中,這些在標準庫中定義(標頭<memory>)。你只是做

    std::unique_ptr<Base> b(new Derived1()); 
    

    而現在,當b葉範圍內,Derived1對象被自動刪除。

    在所有這些例子中,調用虛函數工作以同樣的方式:

    b->func1(); 
    
    +0

    +1,迄今爲止的最佳答案。這裏要拿走的重點是:1.這個類需要一個公共的虛擬析構函數。 2。使用智能指針(最好是'unique_ptr')或引用,永遠不要原始指針。 – Philipp

    1

    使用指針(和他們的陷阱)已被覆蓋,所以我會告訴你如何使用多態而不動態內存分配,可能證明我是一個甚至想到它的異端。

    首先,讓我們看看使用你的原代碼,修補了所以編譯:

    void foo(bool const cond) { 
        Base* b = 0; 
        if (cond) { b = new Derived1(); } 
        else  { b = new Derived2(); } 
    
        b->func1(); 
        delete b; // do not forget to release acquired memory 
    } 
    

    的正常工作假設你有一個virtual析構函數Base

    在一個程序員的發展的下一個合乎邏輯的步驟是使用智能指針,以避免寫deletedelete僅用於初學者和專家庫作家):

    void foo(bool const cond) { 
        std::unique_ptr<Base> b; 
        if (cond) { b.reset(new Derived1()); } 
        else  { b.reset(new Derived2()); } 
    
        b->func1(); 
    } 
    

    ,我們仍然需要virtual當然,它的破壞者爲Base

    讓我們意識到,這個功能做了兩件事情:

    • 選擇給定的條件
    • 做產生的實例一些工作類

    我們可以打破它,例如通過提取構建工作:

    std::unique_ptr<Base> build(bool const cond) { 
        if (cond) { return { new Derived1() }; } 
        return { new Derived2() }; 
    } 
    
    void foo(bool const cond) { 
        std::unique_ptr<Base> b = build(cond); 
        b->func1(); 
    } 
    

    這是大多數人做的事情。

    我要求還有另一種可能,而不是孤立的構建,我們可以隔離的實際工作:

    void dowork(Base& b) { b.func1(); /* and perhaps other things */ } 
    
    void foo(bool const cond) { 
        std::unique_ptr<Base> b(cond ? new Derived1() : new Derived2()); 
        work(*b); 
    } 
    

    ,我們實際上可以把它一步:

    void foo(bool const cond) { 
        if (cond) { 
         Derived1 d; 
         work(d); 
        } else { 
         Derived2 d; 
         work(d); 
        } 
    } 
    

    多態性不需要動態內存分配。

    而且你知道什麼是有趣關於這個最後的例子:

    • 它完美罰款沒有C++ 11移動語義
    • 它不需要在Base
    virtual析構函數
    +0

    好點,我不會稱這種多態 - 雖然只是分支。前者的意義在於,你可以編寫像'void bar(Base&b,Otherstuff o){if(somejunk(o))b.func1();}這樣的函數'''不需要關心什麼不同的派生實例實際上是'Base'。 – leftaroundabout

    +0

    @leftaroundabout:它和其他​​版本一樣多分支,'work'也不關心它的'Base&'參數的動態類型。它也更快(沒有動態內存分配),並且如果'work'在'foo'中內聯,那麼可以優化非虛擬調度;這在其他形式中是不太可能的。 –

    相關問題