0

我正在开发一个 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,引擎都必须提供相同的接口,所以我不认为我可以使用虚拟函数来实现这一点,因为我事先无法从 IA 知道 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 是一个或两个 HardwareBuffers 周围的其他数据它们,如顶点格式、材质等。让它们成为单独的类允许客户端代码让多个网格共享一个公共的 HardwareBuffer 和类似的东西。

4

3 回答 3

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
}

这样你仍然可以从实现中分离出来(例如,你可以把它分成两个独立的部分),但是在你的引擎内部,所有的类都会对彼此有足够的了解以正常工作。

于 2013-06-10T05:06:56.187 回答
0

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

要基于动态类型安全地强制转换,请使用dynamic_cast. 如果你已经知道动态类型而不看 vtable,那么你可以优化dynamic_caststatic_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,引擎都必须提供相同的接口,所以我不认为我可以使用虚拟函数来实现这一点,因为我事先无法从 IA 知道 IB 需要什么。

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

于 2013-06-10T05:21:47.310 回答
0

好的,现在我想我明白你的问题了。您有几个后端几乎没有(双关语)共同点,您需要在隐藏它们差异的引擎中使用它们。

现在的问题是,如果两种类型没有共同点,它们不应该继承自一个共同的 base。当然,像将指针存储在 avoid*中这样的黑客行为只是扫除地毯下的灰尘。所以我们不要那样做。

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

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 抽象很浅,则每个包装器都可能成为您的整个引擎。然后引擎类将退化为一个简单的转发器。还行吧。

于 2013-06-10T05:56:59.417 回答