2012-05-08 105 views
4

我有一個基類,Primitive,從中我推導幾個其它classes-- SpherePlane如何在派生類上強制實施靜態成員?

Primitive強制一些功能,e.g intersect(),在它的子類通過純虛函數。 intersect的計算取決於實例數據,因此將其作爲成員方法是有意義的。

我的問題在於: 我希望每個派生實例都能夠識別它的類型,比如通過一個std::string type()成員方法。由於同一類的所有實例都會返回相同的類型,因此可以使用type()方法。因爲我也希望每個Primitive子類都實現這個方法,所以我還想使它成爲一個純虛函數,就像上面的intersect()一樣。

但是,在C++中不允許使用靜態虛擬方法。 C++ static virtual members?Can we have a virtual static method ? (c++) 問類似的問題,但它們不包括在派生類上強制執行函數的要求。

任何人都可以幫我解決上述問題嗎?

+1

如果你想要多態性爲什麼你想要一個靜態類型?在你的界面中使用基類指針,並讓它在運行時動態地找出類型。這是虛擬方法的重點。 – AJG85

+0

你會怎樣稱呼虛擬靜態? –

+0

我正在考慮通過一個實例進行調用,例如[http://stackoverflow.com/questions/325555/c-static-member-method-call-on-class-instance],甚至通過'this'指針,例如[http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr039.htm] – wsaleem

回答

0

你可以有一個非靜態的虛方法調用靜態方法(或者返回一個靜態字符串),並在每個派生類中適當地實現。

#include <iostream> 
#include <string> 

struct IShape { 
    virtual const std::string& type() const =0; 
}; 

struct Square : virtual public IShape { 
    virtual const std::string& type() const { return type_; } 
    static std::string type_; 
}; 
std::string Square::type_("square"); 

int main() { 

    IShape* shape = new Square; 
    std::cout << shape->type() << "\n"; 

} 

請注意,您無論如何都要落實每個子類中的方法type(),所以你能做的最好的是字符串是靜態的。但是,您可以考慮使用enum而不是字符串,避免在代碼中進行不必要的字符串比較。

現在,回到問題的基本面,我認爲設計有些不妥。你不可能真的有一個在各種形狀上操作的通用交叉函數,因爲交點產生的形狀類型差異很大,即使對於相同類型的形狀(兩個平面可以相交於一個平面,一條線或不相交於所有,例如)。因此,在試圖提供一個通用的解決方案時,您會發現自己在各處執行這種類型的檢查,並且這種增長的形狀會越來越難以維持。

+0

謝謝,我喜歡通過虛擬方法返回靜態字符串的想法。但是,接下來,我會在每個子類中都有一個「static std :: string myType」聲明。這看起來像代碼重複。我可以不使用繼承來避免它嗎? – wsaleem

+0

不幸的是,你無法真正擺脫每個子類的虛擬方法。儘管我已經添加了更多信息和評論。這是一個棘手的問題... – juanchopanza

+0

@juanchopanza你可以看到我的答案。 –

2

由於他們在您提供的鏈接中討論的原因,您不能將虛擬成員設爲靜態。

您對關於在派生類上強制執行函數的要求的問題,通過在抽象基類中使函數爲純虛擬來處理,這將強制派生類必須實現該函數。

4

讓我們考慮一下。我相信你不僅有2個子表面,所以我們來概括一下。

首先想到的是代碼重複,可擴展性和親近性。讓我們來看看這些:

如果你想添加更多的類,你應該儘可能地改變代碼。

由於intersect操作可交換,相交AB應該在相同的位置的代碼,相交BA,因此保持邏輯的類中的代碼本身是不可能的。另外,添加一個新的類並不意味着你必須修改現有的類,而是擴展一個委託類(是的,我們正在進入模式)。

這是當前的結構,我認爲(或類似的,可能是intersect返回類型,但現在並不重要):

struct Primitive 
{ 
    virtual void intersect(Primitive* other) = 0; 
}; 
struct Sphere : Primitive 
{ 
    virtual void intersect(Primitive* other) 
}; 
struct Plane : Primitive 
{ 
    virtual void intersect(Primitive* other); 
}; 

我們已經決定,我們不希望內部Plane交集邏輯或Sphere,所以我們創建了一個新的class

struct Intersect 
{ 
    static void intersect(const Sphere&, const Plane&); 
    //this again with the parameters inversed, which just takes this 
    static void intersect(const Sphere&, const Sphere&); 
    static void intersect(const Plane&, const Plane&); 
}; 

在這裏,您將添加新功能的類和新的邏輯。例如,如果您決定添加Line類,則只需添加方法intersec(const Line&,...)

請記住,添加新類時,我們不想更改現有的代碼。所以我們不能檢查相交函數中的類型。

我們可以爲該(策略模式)一類的行爲,其行爲不同,這取決於類型,我們可以事後擴展:

struct IntersectBehavior 
{ 
    Primitive* object; 
    virtual void doIntersect(Primitive* other) = 0; 
}; 
struct SphereIntersectBehavior : IntersectBehavior 
{ 
    virtual void doIntersect(Primitive* other) 
    { 
     //we already know object is a Sphere 
     Sphere& obj1 = (Sphere&)*object; 
     if (dynamic_cast<Sphere*>(other)) 
      return Intersect::intersect(obj1, (Sphere&) *other); 
     if (dynamic_cast<Plane*>(other)) 
      return Intersect::intersect(obj1, (Plane&) *other); 

     //finally, if no conditions were met, call intersect on other 
     return other->intersect(object); 
    } 
}; 

而在我們原來的方法,我們就必須:

struct Sphere : Primitive 
{ 
    virtual void intersect(Primitive* other) 
    { 
     SphereIntersectBehavior intersectBehavior; 
     return intersectBehavior.doIntersect(other); 
    } 
}; 

一個更清潔的設計將實現一個工廠,抽象出實際類型的行爲:

struct Sphere : Primitive 
{ 
    virtual void intersect(Primitive* other) 
    { 
     IntersectBehavior* intersectBehavior = BehaviorFactory::getBehavior(this); 
     return intersectBehavior.doIntersect(other); 
    } 
}; 

,你甚至不需要intersect是虛擬的,因爲它只是爲每個班級做這個。

如果在添加新的類時,遵循這一設計

  • 無需修改現有代碼
  • 有一個地方的實現
  • IntersectBehavior爲每個新類型
  • 延長在Intersect類中提供新類型的實現

我敢打賭,這可能會進一步完善。

+0

感謝您的詳細解答。我想我在原帖中的表達經濟導致你誤入歧途。 'intersect()'把'const Ray'作爲參數,而不是(指向)其他'Primitive'實例。每個'Primitive'類都知道它是如何與Ray相交的,這似乎是合理的。將'intersect'移動到外面需要自定義的'Intersect'類訪問違反封裝的'Primitive'內部。 – wsaleem

+0

你的類型識別最終歸結爲一個'dynamic_cast',我一般都會收集它的用法,這是不鼓勵的。任何意見? – wsaleem

+0

@wsaleem很好,netiher有一個'id'成員。但'dynamic_cast'已經存在,爲什麼要重新發明輪子? –

1

因爲同一類的所有實例都會返回相同的類型,所以使type()成爲一個靜態方法是有意義的。

不,它不。當您不需要對象的實例來調用函數時,您可以使用靜態方法。在這種情況下,你試圖識別對象的類型,所以你需要一個實例。

無論如何所有對象都共享所有的方法體,所以不需要擔心重複。一個例外是函數內聯時,但編譯器將盡最大努力使開銷最小化,並且如果成本太高,可能會將其變爲非內聯。

P.S.要求類在類層次結構之外標識自身通常是不好的代碼味道。試着找到另一種方式。

+0

''你不需要實例時使用靜態方法......所以你確實需要一個實例。「'謝謝,這是非常真實的。我沒有想到這一點。 ''無論如何所有對象都共享所有方法體,所以不必擔心重複。「'另一個好點。 – wsaleem

+0

你和@ ajg85都認爲我的設計很好奇。讓我詳細說明,然後你可能會提出一個更好的選擇。我想通過'Primitive *'對象調用一個'Primitive :: emitXml()'方法。發出的XML包含例如' ...',這取決於指針指向的'Primitive'對象的類型。我需要'type()'函數來填充'emitXml()'函數中'type'屬性的值。 – wsaleem

+0

@wsaleem,這確實是一個合法的用例。很多時候,意圖是在'if'語句中使用它來根據對象類型獲取不同的行爲,這就是我所指的代碼味道。 –