2010-01-12 60 views
2

我正在使用遺留的框架。可以說'A'是基類,'B'是派生類。這兩個類都執行一些關鍵的框架初始化。 FWIW,它大量使用ACE庫。衍生類構造函數問題的依賴關係

我有一種情況,其中; 'B'的一個實例被創建。但'A'的Ctor取決於一些只能從'B'執行的初始化。

正如我們知道'B'實例化時'A'的Ctor在'B'之前被調用。 virtual機制不能從ctors工作,使用static functions被排除(由於static-initialization-order-fiasco)。

我認爲使用CRTP模式如下: -

template<class Derived> 
class A { 
public: 
    A(){ 
    static_cast<Derived*>(this)->fun(); 
    } 
}; 

class B : public A<B> { 
public: 
    B() : a(0) { 
    a = 10; 
    } 
    void fun() { std::cout << "Init Function, Variable a = " << a << std::endl; } 
private: 
    int a; 
}; 

但是,這是在初始化列表中初始化的類成員,因爲他們尚未執行(FE「A」在上述情況下未定義的值)。在我的情況下,有很多這樣的基於框架的初始化變量。

是否有任何知名的模式來處理這種情況?

由於提前,


更新

基於由dribeas給出的主意,我喚出行動這一問題的臨時解決方案(一個完整的重構呢現在不符合我的時間表)。下面的代碼將表現出同樣的: -

// move all A's dependent data in 'B' to a new class 'C'. 
class C { 
public: 
    C() : a(10) 
    { } 
    int getA() { return a; } 
private: 
    int a; 

}; 

// enhance class A's ctor with a pointer to the newly split class 
class A { 
public: 
    A(C* cptr) 
    { 
    std::cout << "O.K. B's Init Data From C:- " << cptr->getA() << 
std::endl; 
    } 

}; 

// now modify the actual derived class 'B' as follows 
class B : public C, public A { 
public: 
    B() 
    : A(static_cast<C*>(this)) 
    { } 

}; 

有關同一些更多的討論見c.l.C++米this鏈接。 Konstantin Oznobikhin給出了一個很好的通用解決方案。

+2

即UB(未定義行爲)和一個相當惡劣的伎倆編譯器播放。 – 2010-01-12 08:29:01

回答

3

你可以做的最好的事情是重構。讓基類依賴於其派生類型之一是沒有意義的。

我已經看到過這個過程,爲開發人員提供了一些痛苦:擴展ACE_Task類以提供一個週期性線程,該週期性線程可以用具體功能進行擴展,並僅從週期性線程構造函數中激活線程才能發現在測試中並且往往不是它的工作,但是在某些情況下,線程實際上是在大多數派生對象被初始化之前啓動的。

繼承是一種強烈的關係,只有在需要時才應使用。如果你看看boost線程庫(只是文檔,不需要輸入詳細信息)或POCO庫,你會看到它們將問題分成兩部分:線程類控制線程執行並調用傳遞的方法在構造它們的時候:線程控制與將要運行的實際代碼分開,並且將被運行的代碼作爲構造函數的參數接收的事實保證它在線程構造函數被調用之前被構造。

也許你可以在你自己的代碼中使用相同的方法。將功能分成兩部分,無論派生類現在做什麼都應該移到層次結構之外(boost使用函子,POCO使用接口,使用最適合你的任何東西)。如果沒有更好的描述你想要做的事情,我就不能進入更多的細節。

你可以嘗試的另一件事(這是脆弱的,我會建議不要)將B類打破成獨立於A的C類和繼承B類的B類,首先來自C然後來自A那裏有巨大的警告評論)。這將保證C將在A之前構造。然後使C子對象成爲A的參數(通過接口或作爲模板參數)。這可能是最快的黑客攻擊,但不是一個好的攻擊。一旦你願意修改代碼,就做對了。

+0

感謝您的建議。看起來像是我在商店裏進行的一個重要的重構練習。 – Abhay 2010-01-12 09:56:42

+0

@dribeas:你不僅瞭解真正的生產代碼相關問題(不像一些人認爲這是一個愚蠢的問題),也提供了很好的建議,其中一個我用於臨時修復。所以我接受這個答案。謝謝。 – Abhay 2010-01-13 15:02:54

2

首先,如果基類的構造函數依賴於派生類中的構造函數中完成的某些操作,我認爲您的設計很糟糕。它確實不應該是那樣。在基類的構造函數運行時,派生類的對象基本上不存在。

一個解決方案可能是將一個輔助對象從派生類傳遞給基類的構造函數。

+0

它不能幫助它!它是一個'傳統'應用框架。我也考慮過幫助對象的方法,但在我的情況下,這是太多的工作。讓我們看看我們是否找到更好的選擇。謝謝 – Abhay 2010-01-12 08:23:56

2

也許Lazy Initialization是爲你做的。在A中存儲一個標誌,是否已初始化。每當你調用一個方法,檢查標誌。如果它是假的,則初始化A(B的運算符已經運行),並將該標誌設置爲true。

1

這是一個糟糕的設計,正如已經說過的那樣,它是UB。請考慮將這種依賴於一些其他的方法從派生類的構造說「初始化」,並呼籲該initialize方法(或之前的任何地方,你真正需要的基類數據進行初始化)

0

嗯。因此,如果我正確閱讀了這些內容,「A」是遺留代碼的一部分,並且您確信對某些問題的正確答案是使用派生類B。我最簡單的解決方案可能是做一個功能(非OOP)風格的靜態工廠功能;

static B& B::makeNew(...); 

除了你說你遇到靜態初始化命令失敗?我不認爲你會用這種設置,因爲沒有進行初始化。

好的,更多地看問題,「C」需要由「B」完成「A」需要完成的一些設置,只有「A」得到第一個dib,因爲你想要繼承。那麼......假繼承,可以讓你控制施工順序......?

class A 
{ 
    B* pB; 
public: 
    rtype fakeVirtual(params) { return pB->fakeVirtual(params); } 

    ~A() 
    { 
     pB->deleteFromA(); 
     delete pB; 
     //Deletion stuff 
    } 

protected: 
    void deleteFromB() 
    { 
     //Deletion stuff 
     pB = NULL; 
    } 
} 

class B 
{ 
    A* pA; 
public: 
    rtype fakeInheritance(params) {return pA->fakeInheritance(params);} 

    ~B() 
    { 
     //deletion stuff 
     pA->deleteFromB(); 
    } 

protected: 
    friend class A; 
    void deleteFromA() 
    { 
     //deletion stuff 
     pA = NULL; 
    } 
} 

雖然很詳細,我想這應該安全地假繼承,並讓你等待構建後才B已經完成它的事情。它也被封裝,所以當你可以拉你不應該改變比A和B之外的任何

或者,您可能還需要採取退後幾步,並問自己;繼承給我的功能是什麼,我正在嘗試使用,以及如何通過其他方式來實現?例如,CRTP可以用作virtual的替代方案,而策略則是函數繼承的替代方案。 (我認爲這是正確的措詞)。我在上面使用這些想法,只是放棄模板B/C,我只希望A在B上模板,反之亦然。