static_cast接口类到内部引擎实现

时间:2013-06-10 04:51:29

标签: c++

我正在开发3D引擎,假设我有以下接口类:

class IA {
public:
    virtual ~IA() {}
    virtual void doSomething() =0;   
};

class IB {
public:
    virtual ~IB() {}
    virtual void bindA( IA* ) =0;
};

如果要获取“IA”或“IB”类型的对象,则必须从依赖于所使用的后端API的工厂(例如OpenGL)获取它们。 函数IB :: bindA(IA *)需要从IA的实现中访问数据,并实现它对实现类执行static_cast,然后直接访问它的元素。

我想知道你对static_cast的这种特殊用法的看法,你认为这是不好的设计吗?或者你认为没关系?。

无论使用什么后端API,引擎都必须提供相同的接口,所以我不认为我可以使用虚函数来实现这一点,因为我事先无法知道IB对IB的需求。

谢谢:D

修改

事情是引擎有以下两个类:

class IHardwareBuffer {
public:
    virtual ~IHardwareBuffer() {}
    virtual void allocate( .... ) =0;
    virtual void upload( .... ) =0;
};

class IMesh {
public:
    virtual ~IMesh() {}
    virtual bindBuffer( IHardwareBuffer* ) =0;
    ...
};

我“可以”将IMesh和IHardwareBuffer类合并在一起,但这没有多大意义,因为HardwareBuffer只是一个带有顶点数据的“哑”内存块,而Mesh是一个或两个带有硬件缓冲区的硬件围绕它们的其他数据,如顶点格式,材质等。 让它们成为单独的类允许客户端代码有几个网格共享一个共同的HardwareBuffer和类似的东西。

3 个答案:

答案 0 :(得分:1)

在我看来,从设计的角度来看,这实际上是一个非常糟糕的主意。

如果您使用接口(或模拟它们,因为C ++没有这种语言结构),您可以使用它们来发布这些数据,这些数据在其他地方是必需的。因此,如果实现IB的对象必须将IA转换为某些内容来检索其数据,那么显然是一个标志,即IA发布的数据不足或实现IA的对象还应该实现另一个更广泛的界面。

很难说,哪个选项更好(或者可能还有另一个选项),因为我们不知道这里的上下文。一般情况下,如果没有必要,应该避免使用铸造,并且这里不需要


修改

The engine has to provide the same interface no matter what backend API is being used, so I don't think I could achieve this using virtual functions because I can't know beforehand what is needed by IB from IA. - 这是一个糟糕的设计。

引擎应该以这样的方式编写,它完全独立于使用它的实现,而反之亦然。这是使用接口,基类和多态的全部要点:您应该能够编写另一个引擎,将其与现有引擎交换,一切都应该在没有任何实现更改的情况下工作。


编辑(以回应评论):

我认为,更明确的解决方案是投射到另一个界面,而不是具体的实现,即:

class A : public IA, public IInternalA
{
     // Implementation
};

// Inside B:
void B::Process(IA * a)
{
    IInternalA ia = dynamic_cast<IInternalA *>(a);

    if (ia != nullptr)
        // Do something
}

通过这种方式,您仍然可以切断实施(例如,您可以将其切割成两个独立的部分),但是在您的引擎内,所有类都能够充分了解彼此的工作情况正常。

答案 1 :(得分:0)

一个对象具有动态类型,可以在运行时从其vtable和静态类型中读取,这是您在源代码中声明的内容。

要根据动态类型进行安全投射,请使用dynamic_cast。如果您已经知道动态类型而未查看vtable,则可以将dynamic_cast优化为static_cast。这可能会有效地改善性能,只要它有效,这样做就没有错。

过于频繁地转换为派生类引用的代码可能会出现关注点分离的问题。类层次结构的要点是概括。

我建议使用引用,而不是指针,因为如果转换无效,dynamic_cast的引用形式将抛出异常。然后你可以做这样的事情:

// Check dynamic type (and throw exception) for debug build only
#ifndef NDEBUG
#define downcast static_cast
#else
#define downcast dynamic_cast
#endif

Iopengl &glenv = downcast< Iopengl & >( myIA );

如果总是知道实际的动态类型而不去vtable(例如全局opengl标志),那么vtable当然是多余的。你可以用标志和分支代替虚拟调度来编写整个程序。

  

无论使用什么后端API,引擎都必须提供相同的接口,所以我不认为我可以使用虚函数来实现这一点,因为我事先无法知道IB对IB的需求。

正如您所说,抽象基类提供接口,派生类调用后端。您的示例有点粗略,但看起来IA和IB是接口,如果您要实现目标,则必须定义与后端无关的方式......无论实现如何。

答案 2 :(得分:0)

现在好了,我想我理解你的问题。你有几个后端几乎没有任何共同点,你需要在隐藏它们差异的引擎中使用它们。

现在的问题是,如果两种类型没有任何共同之处,它们不应该从公共基础继承。当然,将指针存放在void*中的黑客只是将尘土扫到地毯下面。所以,我们不要这样做。

所以你需要为每个后端提供一个包装器。所有包装器应该符合相同的接口,但就它们的实现而言没有任何共同之处。工厂应该返回一个包装。

class IBackendWrapper
{
  public:
   ... backend pure virtual functions ...
};

class OpenGLBackendWrapper : public IBackendWrapper
{
  public:
   ... backend virtual function immplementations in terms of OpenGL ...
  private:
    ... OpenGL data ...
};

class X11BackendWrapper : public IBackendWrapper
{
  public:
   ... backend virtual function immplementations in terms of X11...
  private:
    ... X11 data ...
};

class BackendFactory
{
  public:
    IBackendWrapper* getbackend();
};

现在您的引擎可以使用IBackendWrapper,而不必担心具体的后端。

如果您的3D抽象很浅,可能会发生每个包装器 您的整个引擎。然后引擎类将退化为一个简单的转发器。还行吧。