2010-03-09 64 views
12

我有一個相當複雜的類層次結構,其中類是相互交叉依賴的:有兩個抽象類A和C,分別包含一個返回C和A實例的方法。在他們的繼承類中,我想使用一種協變類型,在這種情況下,這是一個問題,因爲我不知道如何前向聲明繼承關係。C++:如何避免繼承類中的「無效協變返回類型」而無需投射?

我得到一個「test.cpp:22:error:'虛擬D * B :: outC()'的無效協變返回類型 - 錯誤,因爲編譯器不知道D是C的一個子類。

class C; 

class A { 
public: 
     virtual C* outC() = 0; 
}; 

class C { 
public: 
     virtual A* outA() = 0; 
}; 


class D; 

class B : public A { 
public: 
     D* outC(); 
}; 

class D : public C { 
public: 
     B* outA(); 
}; 

D* B::outC() { 
     return new D(); 
} 

B* D::outA() { 
     return new B(); 
} 

如果我將B :: outC()的返回類型更改爲C *,示例將編譯。有沒有什麼方法可以將B *和D *作爲返回類型保留在繼承類中(這對我來說有一種直觀的方式)?

+2

你真的需要這種類型的糾纏? (稱它耦合可能有點短) – 2010-03-09 17:17:34

+0

有時候這不是語言問題,這是我們嘗試使用它的方式的一個問題。如果你的兩個層次結合得如此緊密,我認爲你最好用一個層次結構(將'A'與'C'和'B'與'D'融合在一起),因爲它看起來好像它們不能互不干擾。 – 2010-03-09 19:38:37

+0

嗯,我有兩種類型的類:一個任務的規範和一個實際執行任務的執行程序(啓動幾個線程)。 規範本身應該是任務的工廠,每個執行者都需要訪問它的規範。他們的任務有各種規格。所以執行者包裝了一個規範,所以耦合只在一個方向上很強,而在另一個方向上它只是工廠方法。 – Searles 2010-03-09 21:34:09

回答

7

我知道沒有辦法直接耦合協變成員在C++中。你將不得不添加一個圖層,或者自己實現協變回報。

對於第一種選擇

class C; 

class A { 
public: 
     virtual C* outC() = 0; 
}; 

class C { 
public: 
     virtual A* outA() = 0; 
}; 


class BI : public A { 
public: 
}; 

class D : public C { 
public: 
     BI* outA(); 
}; 

class B: public BI { 
public: 
     D* outC(); 
}; 

D* B::outC() { 
     return new D(); 
} 

BI* D::outA() { 
     return new B(); 
} 

和第二

class C; 

class A { 
public: 
     C* outC() { return do_outC(); } 
     virtual C* do_outC() = 0; 
}; 

class C { 
public: 
     virtual A* outA() = 0; 
}; 


class D; 

class B : public A { 
public: 
     D* outC(); 
     virtual C* do_outC(); 
}; 

class D : public C { 
public: 
     B* outA(); 
}; 

D* B::outC() { 
     return static_cast<D*>(do_outC()); 
} 

C* B::do_outC() { 
     return new D(); 
} 

B* D::outA() { 
     return new B(); 
} 

請注意,此第二個選項是什麼是由編譯器隱式進行(有一些靜態檢查該的static_cast有效) 。

0

由於客戶端的期望,你不能這樣做。當使用C實例時,你不能說出它是哪種C(D或其他)。因此,如果將B指針(由調用派生類產生,但在編譯時不知道)存儲到A指針中,我不確定所有內存的東西都是正確的。

當您調用多態類型的方法時,運行時環境必須檢查對象的動態類型,並且它會移動指針以適應您的類層次結構。我不確定你應該依賴協變性。看看this

+1

你誤會了。協變返回類型是*允許*。如果客戶端期望一個'A'指針,但是給它一個'B'指針,那麼可以找到它,因爲'B' *的所有實例都是'A'的實例。在這種情況下,問題是編譯器無法在'B :: outC'的聲明中驗證新的返回類型'D'是原始返回類型'C'的後代。如果編譯器可能已經看到了'D'的完整定義,它將允許新的簽名。請注意,編譯器不會對'D :: outA'發出任何抱怨,因爲它知道'C'是'A'的後代。 – 2010-03-09 16:46:20

+1

在我的示例中,類D中的協變類型返回類型不是問題。 – Searles 2010-03-09 22:23:02

4

據我所知,沒有明確鑄造就沒有辦法做到這一點。問題是B類的定義不能知道DC的子類,直到它看到類D的完整定義,但類D的定義無法知道BA的子類,直到它看到類B的完整定義,所以你有循環依賴。這不能用前向聲明來解決,因爲前向聲明遺憾地不能指定繼承關係。

嘗試使用模板which I found can be solved嘗試實施協變clone()方法存在類似問題,但類似的解決方案在此仍然失敗,因爲循環引用仍然無法解析。

+0

這確實是一個循環依賴的問題。當你嘗試使用交叉相關的typedefs,枚舉,字段等時也是如此。+1 – doc 2010-03-09 17:16:00

+0

每個人都被這個「克隆」問題所困擾......我不喜歡這個解決方案給出了它的包裝部分艱難, 'typedef'不能被轉發聲明,如果你的客戶必須知道不使用未修飾的名字,這是一種痛苦:/ – 2010-03-09 19:36:58