首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Pimpl成语与多重虚拟继承相结合

Pimpl成语与多重虚拟继承相结合
EN

Stack Overflow用户
提问于 2014-11-03 18:24:02
回答 1查看 982关注 0票数 1

背景

我只是尝试探索一些可用的模式和巧妙的使用可能性(在c++中)。有很多关于如何使用多个虚拟继承或pimpl成语的信息,也有关于桥梁或嵌套泛化的很好的主题,但是我没有找到一个很好的主题。

我知道类似的问题,例如:

Pimpl idiom with inheritance

但我认为这不是一个重复,值得它自己的答案。我的方法可能是绝对愚蠢的,所以请告诉我一些有用的方向。这不是家庭作业,因此对答案/语言/范例没有任何限制,但为了不讨论个人偏好,请试着推理为什么某个特定模式/解决方案是有效的,或者因为其他提出的解决方案是错误的/容易出错的/.。

问题

实现以下结构(可能是组合而不是继承)的好方法是什么?如果重用代码、更容易读/写、更容易扩展是主要的重点(也许您可以考虑其他问题),那么不同的答案可能是有效的。

我在当前实现中看到的主要问题是,这一点根本无法扩展,因为我们为获得了一个类,每个要建模的属性的值的每个组合都是这样。为了简单起见,为了简单起见,我使用了一个带有属性投影(FirstPerson或FreeLook)和行为的相机,但是对于属性为P1、P2、P3、P4的任何实体A,它们都可以是多个子类型中的一个,P1.2,.P2=>P2.1,P2.2,.

投影应确定执行

代码语言:javascript
复制
virtual const glm::mat4& getProjectionMatrix() = 0;

而行为则应界定

代码语言:javascript
复制
virtual const glm::vec3& getForwardVector() = 0;

可能的解决方案1:将pimpl成语与多个虚拟继承相结合

Sidenote:我使用名称_this作为pimpl指针,因为从我的角度来看,变量导致了非常可读的代码。当然,如果存在同名的成员(不太可能,但确实可能),那么就有可能混淆这个和_this。

Camera.h

代码语言:javascript
复制
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

代码语言:javascript
复制
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的数据器和函数,这可能是必要的(假设overriden虚拟方法的返回值取决于私有数据成员的值)。

而且,这看起来像一个实现,代码生成器可以很好地支持它,因为您只需要对一个新子类进行正确的继承。

可能的解决办法2:组成

我能想到的另一个解决方案就是使用构图。

Camera.h

代码语言:javascript
复制
class Camera{
protected:
    ProjectionType _projectionType;
    CameraBehaviour _cameraBehaviour;
private:
    class Impl;
    std::unique_ptr<Impl> _this;
};

如果ProjectionType或CameraBehaviour子类依赖于相机的任何值::Impl,则需要传递实例,我认为这会使代码非常混乱。另一方面,我们最终得到的类要少得多,因为每个projectionType需要一个类,每个cameraBehaviour需要一个类。如果我们有许多类型和行为的可能值,这可能是解决方案。

可能的解决方案3:继承

当然,我们可以只使用相机的子类。在这种情况下,可能使用pimpl,也可能不使用pimpl,因为所有需要受保护的visibilty的数据成员都不应该是pimpl的一部分。

Camera.h

代码语言:javascript
复制
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

代码语言:javascript
复制
class PerspectiveFreeLookCamera: virtual AbstractCamera{
/*Override both methods*/
};

PerspectiveFirstPersonCamera.h

代码语言:javascript
复制
class PerspectiveFirstPersonCamera: virtual AbstractCamera{
/*Override both methods*/
};

Ortographic类也是如此。在这里,虚拟方法将作为所有透视图/轨道图实现多次.类共享方法的相同实现。

代码语言:javascript
复制
virtual const glm::mat4& getProjectionMatrix() = 0;       

当所有FreeLook/FirstPerson类共享相同的实现时,

代码语言:javascript
复制
virtual const glm::vec3& getForwardVector() = 0;

谢谢你花时间,我希望这是一个很好的问题,因为我已经对它作了相当多的思考,希望它对许多人来说是有趣的:)

编辑:如果有人能为这个问题想出一个更好的标题,请随意编辑,我很难找到一个好的标题。

可能的解决方案4:模板(添加4.11 19:00 GMT+2)

基于Yakk的回答,我研究了一个模板解决方案。

Camera.h

代码语言:javascript
复制
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

代码语言:javascript
复制
#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

代码语言:javascript
复制
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 */
}
};

现在可以用它了

代码语言:javascript
复制
Camera * c = new Camera(new Camera::Impl<Projection::Ortographic,Behaviour::FreeLook>());
c->getProjectionMatrix();

优点:除了明显的AbstractImpl子类的动态类型之外,这大部分应该是静态类型的。可以在运行时切换摄像机的具体实现。新的特性可以很容易地添加。使用这种方法也是可能的,因为所需要的只是添加AbstractImpl的子类。

缺点:调试可能会变得相当痛苦。只有在尝试这个过程中,我才意识到,尽管在编译/链接期间可能会出现许多错误,但要找到问题仍然要困难得多。

我很感谢对解决方案4的一些反馈,并且因为Yaaks的实现而添加了这些信息。我正确地使用了你的建议吗?我是否错误地监督了某些事情或错误地使用了模板--因为我以前没有使用过它们。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-11-03 19:08:04

代码语言:javascript
复制
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中存储不同的数据,则可以执行以下一些特性:

代码语言:javascript
复制
template<class Tag>
struct camera_data;

并将camera_data<projection_tag>camera_data<location_tag>存储在_impl中,但是如果我们要这么做的话,我会重新考虑这个方法。

我们甚至可以更进一步,让我们的impl将每个标记传递给每个空闲函数,让空闲函数计算出需要关心的标记。

代码语言:javascript
复制
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,或者等待概念精简。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26720521

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档