2010-05-26 69 views
3

由於我的問題聽起來很混亂,我認爲最好清楚說明我想達到的目標。派生類中的C++成員函數模板,如何從基類中調用它?

我有(忽略繼承現在,專注於X):

class Base {}; 

class X : public Base { 
private: 
    double m_double; 
public: 
    template<class A> friend 
    void state(A& a, const X& x) { 
     data(a, x.m_double, "m_double"); 
    } 
}; 

我現在可以將任意新的類,基於該數據功能是如何超載,我們將把這些不同的行動作爲訪問者在以下幾點:

class XmlArchive {...}; //One Accessor 
template<class T> 
void data(XmlArchive& a, const T& t, const std::string& str) { 
//read data and serialize it to xml Archive 
} 

class ParameterList {...}; //Another Accessor 
template<class T> 
void data(ParameterList& a, const T& t, const std::string& str) { 
//put data in a parameter list 
} 

然後我就可以寫:

X myX; 

XmlArchive myArchive; 
state(myArchive, myX); 

ParameterList myParameters; 
state(myParameters, myX); 

奇妙的是,代碼重用! :D然而,以下(明顯)失敗:

Base* basePtr = new X; //This would come from factory really, I should not know the type X 
state(myParameters, *basePtr); //Error 

目標是使最後一次呼叫成功。我已經考慮(以及爲什麼它是不能接受的):

第一種選擇:讓所有訪問者從一個公共基類繼承,說AccessorBase,然後在基地

virtual state(AccessorBase& a) const = 0; 

和實施所需的代碼X(調用狀態的語法稍有不同,但可以修復)。 問題是,AccessorBase將需要爲每個可能的類型提供一個虛擬函數,它將作爲數據函數中的第二個參數。由於這些類型可以是用戶定義的類(請參閱類構成的情況,X中有Y作爲數據成員),但我不明白如何使此策略可行。

第二個選項:在每個訪問器的Base中創建一個虛函數。這違反了開放/關閉原則,因爲添加一個新的Accessor類(比如TxtArchive)將需要修改基類。

我明白爲什麼虛擬成員函數不能被模板化(可能在不同的編譯單元中有不同的vtbls)。 但是,它似乎應該有解決這個問題的方法...... Base知道它確實是X類型的,Accessor的類型總是顯式的,所以它是尋找調用方法的問題(對於XmlArchive類型的存取器):

state(xmlArchive, x); //xmlArchive of type XmlArchive, x of type X 

這將產生結果。

綜上所述,起來,我想電話:

state(myParameters, *basePtr); 

成功,如果basePtr指向一個派生類與呼叫兼容的功能模板,並在其他方面拋出異常。我發現boost :: serialize做了類似的事情,但我無法弄清楚它是如何的(它可能是通過模板在C++中重新實現繼承關係,我看到一個This()函數返回了最多的派生指針但它變得非常混亂......)

再次感謝您的幫助!

回答

1

您可以使用此位訪客模式:如果你不喜歡需要落實各不同on_data類型的

class IVisitor 
{ 
    public: 
    virtual void on_data(double, const char *)=0; 
    virtual void on_data(std::string, const char *)=0; 
}; 

class Base 
{ 
    public: 
    virtual void visit(IVisitor * v)=0; 
}; 

class X : public Base 
{ 
    private: 
    double m_double; 
    std::string m_string; 
    public: 
    void visit(IVisitor * v) 
    { 
     v->on_data(m_double, "m_double"); 
     v->on_data(m_string, "m_string"); 
    } 
}; 

Base * base = ...; 
XmlArchive ar; // This must derive from IVisitor 
base->visit(&ar); 
ParameterList pl; // This must derive from IVisitor 
base->visit(pl); 

IVisitor中,您可以使用boost::any - 但根據我的經驗,該函數實現中所需的分支不值得。

1

我相信你可以使用dynamic_cast或C風格轉換來做到這一點,只要你知道你有一個X對象,將Base *轉換爲X *並調用你的方法(在你的例子中應該是靜態的。)如果你的繼承鏈更深,你不知道在編譯時是否有X,Y或Z,那麼你仍然可以這樣做,但是你需要啓用RTTI。

所以總結起來:

X::state(myParameters, *(X*)(basePtr)); 

或者,如果您啓用RTTI:

X::state(myParameters, *dynamic_cast<X*>(basePtr)); 

在X,Y,Z的情況下,你需要問心無愧ž三個分支::狀態,Y :: state和X :: state,根據basePtr的運行時類型調用正確的函數。

switch(basePtr->get_type()) 
{ 
    case TYPE_X: 
     X::state(myParameters, *(X*)(basePtr)); 
     break; 
    case TYPE_Y: 
     Y::state(...); 
     break; 
} 

你明白了。

+0

但正如我在後文中所述,我知道我知道我有X,因此調用正確函數的責任應該委託給派生類本身(XY和Z或任何其他從Base繼承的),它知道關於它的自我類型。 – stepelu 2010-05-26 16:34:14

+0

是的,這就是爲什麼我說你需要RTTI(或者存儲類型X,Y,Z的Base中的一些枚舉字段),因爲你需要根據對象的_runtime_類型採用不同的分支。你不能使用虛擬函數,這隻能讓你使用某種形式的RTTI,或者可能用仿函數來模擬虛擬函數。無論哪種方式,您或編譯器都必須將某些內容添加到Base。 – Eloff 2010-05-26 20:50:22

0

不知道我完全理解你的要求的參數,但我的蜘蛛感覺被指示給你之後是Curiously Recurring Template Pattern的應用:

class StateBase { 
    virtual void state() = 0; 
}; 

template<typename T> 
class StateMixin : public StateBase { 
    void state() { 
    T::data(); 
    } 
}; 

class MyType : public StateMixin<MyType> 
{ 
    void data() {}; 
} 

這裏,狀態()由StateMixin定義,可從任何StateBase實例調用,但由編譯器爲每個用戶定義類型實現數據分別實例化。

0

朋友聲明添加到您的基礎,所以它成了

class Base 
{ 
    template<class A> friend void state(A& a, const X& x); 
}; 
相關問題