2014-11-03 37 views
1

背景

我只是想探索出位可用的圖案和巧妙的使用的可能性(在C++)的結合。有很多關於如何使用多個虛擬繼承或pimpl習語的信息,並且關於橋接或嵌套泛化有很多不錯的主題,但是我無法爲此找到一個好主題。PIMPL方法具有多個虛擬繼承

我所知道的一樣(在某種程度上)類似的問題:

Pimpl idiom with inheritance

,但我認爲這不是一個重複的,值得它自己的答案。我的方法可能是絕對愚蠢的,所以請提醒我一些有用的方向。這不是一項家庭作業,因此對答案/語言/範例沒有任何限制,但是如果沒有關於個人偏好的討論,請嘗試對某個特定模式/解決方案有效的原因進行推理,原因可能是其他解決方案錯誤/容易出錯/ ...。

問題

什麼是執行以下結構的好辦法(也許組成,而不是繼承?)。如果重用代碼,更容易讀/寫,更容易擴展是主要關注點(也許您可以想到其他問題?),那麼有可能不同的答案是有效的:

我在當前實現中看到的主要問題這是不可擴展的,因爲我們每得到一個類的組合我們要建模的每個屬性的值。爲了簡單起見,我使用具有屬性Projection(Ortographic或Perspective)和Behavior(FirstPerson或FreeLook)的Camera,但對於具有屬性P1,P2,P3,P4的任何實體A,這個想法可以是相同的每個可以是一個出許多亞型的P1 => P1.1,P1.2,... P2 => P2.1,P2.2,...

投影應確定

virtual const glm::mat4& getProjectionMatrix() = 0; 
實施

而行爲應定義

virtual const glm::vec3& getForwardVector() = 0; 

可能的解決方案1的實現:康貝ne pimpl成語與多個虛擬繼承

旁註:我使用名稱_this作爲pimpl指針,因爲_this->變量導致從我的角度來看非常可讀的代碼。當然,如果存在具有相同名稱的成員(不太可能但實際上可能),則存在將此混淆的危險。

Camera.h

class Camera{ 
protected: 
    /*Pimpl Idiom extended by multiple virtual inheritance for the AbstractImpl subclasses*/ 
    class AbstractImpl; 
    std::unique_ptr<AbstractImpl> _this; 
    /*Private subclasses for AbstractImpl*/ 
    class Ortographic; 
    class Perspective; 
    class FirstPerson; 
    class FreeLook; 
    class OrtographicFirstPerson; 
    class OrtographicFreeLook; 
    class PerspectiveFirstPerson; 
    class PerspectiveFreeLook;   
}; 

AbstractImpl.h

class Camera::AbstractImpl 
{ 
public: 
    /*Also contains all private data members of class Camera (pimpl idiom)*/ 
    virtual const glm::vec3& getForwardVector() = 0; 
    virtual const glm::mat4& getProjectionMatrix() = 0;  
}; 
class Camera::Ortographic:virtual Camera::AbstractImpl{public:virtual const glm::mat4& getProjectionMatrix() override;}; 
class Camera::Perspective:virtual Camera::AbstractImpl{public:virtual const glm::mat4& getProjectionMatrix() override;}; 
class Camera::FirstPerson:virtual Camera::AbstractImpl{public:virtual const glm::vec3& getForwardVector() override;}; 
class Camera::FreeLook:virtual Camera::AbstractImpl{public:virtual const glm::vec3& getForwardVector() override;}; 

class Camera::OrtographicFirstPerson:virtual Camera::Ortographic,virtual Camera::FirstPerson{}; 
class Camera::OrtographicFreeLook:virtual Camera::Ortographic, virtual Camera::FreeLook{}; 
class Camera::PerspectiveFirstPerson:virtual Camera::Perspective, virtual Camera::FirstPerson{}; 
class Camera::PerspectiveFreeLook:virtual Camera::Perspective, virtual Camera::FreeLook{}; 

這顯然不是每個屬性,但它看起來像第一眼看上去很多屬性或值的情況下,非常好用的該代碼可以很好地重用,因爲這兩種虛擬方法都是每次必需實施一次。此外,我認爲AbstractImpl的子類的部分代碼非常好讀,因爲它感覺像分配屬性。 「我希望這個班有這個屬性」。此外,所有子類都可以完全訪問AbstractImpl的數據成員和函數,這可能是必要的(假設被覆蓋的虛擬方法的返回值取決於私有數據成員的值)。

此外,這看起來像一個實現,可以由代碼生成器很好地支持,因爲您只需要對新子類進行正確的繼承。

可能的解決方案2:合成

我想到的另一個解決方案就是使用合成。

Camera.h

class Camera{ 
protected: 
    ProjectionType _projectionType; 
    CameraBehaviour _cameraBehaviour; 
private: 
    class Impl; 
    std::unique_ptr<Impl> _this; 
}; 

如果ProjectionType或CameraBehaviour sublcasses取決於相機的任何值::默認地將Impl的實例,有必要通過,我認爲這會弄亂代碼很多。在贊成方面,我們最終以較少的課程,因爲我們需要每個投影類型一個類和每個相機一個類。如果我們有許多可能的類型和行爲值,這可能是解決方案。

可能的解決方案3:繼承

當然,我們可以使用Camera的子類。在這種情況下,根本不可能使用pimpl,因爲所有需要受保護可見性的數據成員都不應該成爲pimpl的一部分。

Camera.h

class AbstractCamera{ 
protected: 
    /*All protected data members*/ 
private: 
    /*Maybe pimpl idiom makes no sense here because most of the variables might be protected*/ 
    class Impl; 
    std::unique_ptr<Impl> _this; 
}; 

PerspectiveFreeLookCamera.h

class PerspectiveFreeLookCamera: virtual AbstractCamera{ 
/*Override both methods*/ 
}; 

PerspectiveFirstPersonCamera.h

class PerspectiveFirstPersonCamera: virtual AbstractCamera{ 
/*Override both methods*/ 
}; 

和Ortographic類相同。在這裏,虛擬方法將實施多次爲所有透視/ Ortographic ...類共享相同的實現的方法

virtual const glm::mat4& getProjectionMatrix() = 0;  

雖然所有FreeLook /第一人稱類分擔

virtual const glm::vec3& getForwardVector() = 0; 

相同的實現感謝您花時間,我希望這可以被認爲是一個很好的問題,因爲我已經對它進行了相當多的考慮,因此它對於希望很多人很有意思:)

編輯:如果有人可以想象一個更好的標題FO這個問題,請隨時編輯,我很難找到一個好的標題。

可能的解決方案4:模板(由4.11 19:00 GMT + 2)

基於我曾經在模板的解決方案工作Yakk的答案。

相機。^ h

class Camera 
{ 
public:  
    /*Forward declaration of public classes of camera 
    of course it would be possible to use their own header files to have a stricter one class 
    per file nature but i do not really see the need for this*/ 
    template<typename t_projection, typename t_behaviour> 
    class Impl; 
    class AbstractImpl; 
    Camera(AbstractImpl *impl); 
    ~Camera(); 
    /* All other public functions for camera*/ 

    /* Example method we want to implement based on tags/traits */ 
    const glm::mat4& getProjectionMatrix(); 
private: 
    std::unique_ptr<AbstractImpl> _this; 
}; 

Camera.cpp

#include "Camera.h" 
#include "Impl.h" 
Camera::Camera(AbstractImpl *impl) : _this(impl){} 
Camera::~Camera() = default; 
/*And all implementations*/ 
/*PIMPL Facade function*/ 
const glm::mat4& Camera::getProjectionMatrix(){ 
    return _this->getProjectionMatrix(); 
} 

Impl.h

namespace Projection{ 
    struct Ortographic{}; 
    struct Perspective{}; 
} 
namespace Behaviour{ 
    struct FirstPerson{}; 
    struct FreeLook{}; 
} 
/* Abstract base class for all different implementations */ 
class Camera::AbstractImpl 
{ 
public: 
/* Contains all PIMPL-Idiom members of camera */ 
/* Contains the pure virtual function for our trait/tag-based method */ 
virtual const glm::mat4& getProjectionMatrix() = 0; 
}; 

template<typename t_projection, typename t_behaviour> 
class Camera::Impl : public Camera::AbstractImpl 
{ 
public: 
Impl(){} 
const glm::mat4& getProjectionMatrix(){ 
    return getProjectionMatrix(t_projection{}); 
} 
const glm::mat4& getProjectionMatrix(Projection::Ortographic){ 
    /* Code for Ortographic Projection */ 
} 
const glm::mat4& getProjectionMatrix(Projection::Perspective){ 
    /* Code for Perspective Projection */ 
} 
}; 

而這個現在可以使用的方式

Camera * c = new Camera(new Camera::Impl<Projection::Ortographic,Behaviour::FreeLook>()); 
c->getProjectionMatrix(); 

好處:除了AbstractImpl子類的明顯動態類型之外,這應該大部分是靜態類型的。在運行時可以切換相機的具體實現。新特徵可以很容易地添加。也可以使用這種方法,因爲所需要的只是添加AbstractImpl的子類。

缺點:調試會變得非常痛苦。在嘗試這個過程中,我一直意識到,儘管在編譯/鏈接期間很多錯誤可能會顯示出來,但仍然很難找到問題。

我會欣賞一些關於解決方案4的反饋,以及由於Yaaks實現而添加的。我是否正確使用了您的建議?我是否曾經錯誤地監督過某件事情或使用過模板 - 因爲我以前沒有使用它們。

+0

您能否解釋接受的解決方案如何解決這個問題?可悲的是,答案並不包含任何討論,我真的不明白這裏的答案與多重(鑽石)繼承有什麼關係? 編輯︰看起來像這個問題有關的評論已被刪除 – NoxMortem 2014-11-03 18:57:58

回答

1
struct ortographic_tag {}; 
struct perspective_tag {}; 
struct first_person_tag {}; 
struct freelook_tag {}; 

template<class projection_tag, class location_tag> 
struct camera_impl : Camera::AbstractImpl { 
    virtual const glm::mat4& getProjectionMatrix() override { 
    return get_projection_matrix(*this, projection_tag{}); 
    } 
    virtual const glm::vec3& getForwardVector() override { 
    return get_forward_vector(*this, location_tag{}); 
    } 
}; 

template<class camera> 
const glm::mat4& get_projection_matrix(camera const& c, ortographic_tag) { 
    // code 
} 
template<class camera> 
const glm::mat4& get_projection_matrix(camera const& c, perspective_tag) { 
    // code 
} 
template<class camera> 
const glm::vec3& get_forward_vector(camera const& c, first_person_tag) { 
    // code 
} 
template<class camera> 
const glm::vec3& get_forward_vector(camera const& c, freelook_tag) { 
    // code 
} 

camera_impl< x, y >將各種虛擬方法轉發到非虛函數。

如果你需要不同的數據存儲在_impl根據投影/位置,你可以做一些特質:

template<class Tag> 
struct camera_data; 

和店中店一個camera_data<projection_tag>camera_data<location_tag>_impl,但如果我們是走的太遠了我會重新考慮這種方法。

我們甚至可以更進一步,讓我們的impl只是將每個標籤傳遞給每個免費功能,並讓免費功能找出需要關注的標籤。

template<class... Tags> 
struct many_tags {}; 
template<class T0, class...Tags> 
struct many_tags:T0, many_tags<Tags...> {}; 

many_tags<a, b, c>,如果他們只是在標籤a超載,它將匹配。如果有超過一個的超載,除非他們自己處理它,否則你會得到不明確的結果。如果他們想要a或b,他們必須使用is_base_of編寫自定義SFINAE,或者等待概念lite。

+0

謝謝你,這是一個非常有趣的方法,我必須首先想通過它選擇它作爲一個答案 - 雖然它看起來非常有前途。另外我想給其他用戶有機會對此評論! – NoxMortem 2014-11-03 19:13:54

+0

該解決方案的缺點是,在運行期間無法正確切換攝像機嗎? – NoxMortem 2014-11-03 20:43:59

+0

@NoxMortem我不明白爲什麼。我基本上使用標籤分派來生成你的2x2實現,並且通過使類的方法派發爲釋放(基於標籤分派的)函數來將類生成與「方法」代碼解耦。可能在2x2的情況下進行工程設計,但它可以很好地擴展到NxN。 – Yakk 2014-11-03 20:59:31