2016-02-26 102 views
2

所以我最近不小心從基類的構造函數中調用了一些虛函數,即Calling virtual functions inside constructorsC++:構造派生類時自動運行函數

我意識到我不應該這樣做,因爲虛擬函數的覆蓋不會被調用,但我怎樣才能實現一些類似的功能?我的用例是我想要一個特定的函數在構造對象時運行,並且我不希望編寫派生類的人不必擔心它正在做什麼(因爲他們當然可以調用這個東西)他們的派生類構造函數)。但是,需要被調用的函數恰好會調用一個虛函數,我想讓派生類能夠覆蓋它們是否需要。

但是因爲一個虛擬函數被調用,所以我不能將這個函數粘貼到基類的構造函數中,並讓它自動以這種方式運行。所以我似乎被卡住了。

有沒有其他的方法來實現我想要的?我碰巧正在使用CRTP從基類訪問派生類中的其他方法,我可以在構造函數中使用它而不是虛函數嗎?或者現在的問題很多?我想也許它可以工作,如果被調用的函數是靜態的?

EDIT2:也剛剛發現這個類似的問題:Call virtual method immediately after construction

+4

如果函數調用是構造派生類所必需的,並且是特定於該類的,那麼聽起來好像應該從派生類的構造函數中調用它。想要從一個技術上還不存在的對象調用方法並不是一個好主意。 –

+0

那麼我想運行的函數是一種運行時驗證程序,以確保它們寫出的派生類符合某些要求。它需要訪問虛擬功能以檢查它們是否正常工作。 –

+0

在這種情況下,@ Jarod42提出的工廠方法可能是您最好的選擇。 –

回答

2

如果真的需要,你可以訪問工廠。

你可以這樣做:

template <typename Derived, typename ... Args> 
std::unique_ptr<Derived> Make(Args&&... args) 
{ 
    auto derived = std::make_unique<Derived>(std::forward<Args>(args)); 
    derived->init(); // virtual call 
    return derived; 
} 
+0

嗯,有趣,但可悲的是,沒有工廠可以使用。 –

1

有沒有簡單的方法來做到這一點。一種選擇是使用所謂的虛擬構造函數習慣用法,隱藏基類的所有構造函數,並暴露靜態的'create' - 它將動態地創建一個對象,調用你的虛擬覆蓋並返回(智能)指針。

這很難看,更重要的是,限制你動態創建的對象,這不是最好的。

但是,最好的解決方案是儘量少使用OOP。 C++強度(與流行的觀點相反)在於它是非OOP特定的特徵。考慮一下 - 標準庫中唯一的多態類型是流,大家討厭(因爲它們是多態的!)

+0

我喜歡流:p。但是,嗯,我同意這個解決方案聽起來像個壞主意......不知道如何重新考慮這個設計。 –

+0

@BenFarmer,你呢?他們的表現呢? – SergeyA

+0

我從來不用擔心他們的速度。他們很慢? –

-1

似乎你想要這個,或需要更多細節。

class B 
{ 
    void templateMethod() 
    { 
     foo(); 
     bar(); 
    } 

    virtual void foo() = 0; 
    virtual void bar() = 0; 
}; 

class D : public B 
{ 
public: 
    D() 
    { 
     templateMethod();   
    } 

    virtual void foo() 
    { 
     cout << "D::foo()"; 
    } 

    virtual void bar() 
    { 
     cout << "D::bar()"; 
    } 
}; 
0

我想要運行的特定功能每當對象被構造,[...它]在轉恰好調用虛函數,我想允許派生類的能力如果他們想要,可以覆

這可以,如果你願意住有兩個限制,可以輕鬆完成:

  1. 建設者在整個類層次結構必須是非公開的,因而
  2. 工廠模板類必須用於構造派生類。

這裏,「特定功能」是Base::check,虛擬功能是Base::method

首先,我們建立基類。它必須滿足兩個要求:

  1. 它必須與MakeBase,其檢查類。我假設你想Base::check方法是私人的,只能在工廠使用。如果它是公開的,當然你不需要MakeBase
  2. 構造函數必須受到保護。

https://github.com/KubaO/stackoverflown/tree/master/questions/imbue-constructor-35658459

#include <iostream> 
#include <utility> 
#include <type_traits> 
using namespace std; 

class Base { 
    friend class MakeBase; 
    void check() { 
     cout << "check()" << endl; 
     method(); 
    } 
protected: 
    Base() { cout << "Base()" << endl; } 
public: 
    virtual ~Base() {} 
    virtual void method() {} 
}; 

模板化CRTP工廠從一個基類,與Base朋友派生,並因此先後獲得了私人檢查方法;它也可以訪問受保護的構造函數以構建任何派生類。

class MakeBase { 
protected: 
    static void check(Base * b) { b->check(); } 
}; 

工廠類可以發出一個可讀的編譯時錯誤消息,如果您不小心使用它不是從Base派生的類:

template <class C> class Make : public C, MakeBase { 
public: 
    template <typename... Args> Make(Args&&... args) : C(std::forward<Args>(args)...) { 
     static_assert(std::is_base_of<Base, C>::value, 
        "Make requires a class derived from Base"); 
     check(this); 
    } 
}; 

派生類必須有一個受保護的構造:

class Derived : public Base { 
    int a; 
protected: 
    Derived(int a) : a(a) { cout << "Derived() " << endl; } 
    void method() override { cout << ">" << a << "<" << endl; } 
}; 

int main() 
{ 
    Make<Derived> d(3); 
} 

輸出:

Base() 
Derived() 
check() 
>3< 
0

如果你看看其他人如何解決這個問題,你會注意到他們只是將調用初始化函數的責任傳遞給客戶端。以MFC的CWnd爲例:你有構造函數,你有Create,你必須調用這個虛函數才能擁有一個合適的CWnd實例:「這些是我的規則:構造,然後初始化;服從,否則你會遇到麻煩「

是的,它很容易出錯,但它比替代方案更好:「有人認爲這個規則是一個實現工件。不是這樣。事實上,從構造函數中實現與其他函數完全一樣的不安全的調用虛函數的規則會更容易。但是,這意味着沒有虛擬函數可以寫入依賴基類建立的不變量。這將是一個可怕的混亂。「 - Stroustrup。我認爲,他的意思是,將虛擬表指針設置爲指向派生類的VT會更容易,而不是在構造函數調用從底層向下時將其更改爲當前類的VT。

我知道我不應該這樣做,因爲虛擬函數的覆蓋範圍將不會被調用,...

假設調用虛函數會工作,你想要的方式,你不應該因爲不變量而做到這一點。

class B // written by you 
{ 
public: 
    B() { f(); } 
    virtual void f() {} 
}; 

class D : public B // written by client 
{ 
    int* p; 
public: 
    D() : p(new int) {} 
    void f() override { *p = 10; } // relies on correct initialization of p 
}; 

int main() 
{ 
    D d; 
    return 0; 
} 

這將是什麼,如果有可能通過的D VT調用從BD::f?您將使用未初始化的指針,這很可能會導致崩潰。

...但我怎麼能實現一些類似的功能?

如果你願意違反規則,我猜可能有可能得到想要的虛擬表的地址並從構造函數調用虛函數。

相關問題