2013-06-10 1 views
0

Я занимаюсь разработкой 3D-движок, предположим, что у меня есть следующие классы интерфейса:static_cast класс интерфейс для внутренней реализации двигателя

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 от IA.

спасибо: D

Редактировать

Дело в том, двигатель имеет следующие два класса:

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

и

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

I "может" слить Классы IMesh и IHardwareBuffer вместе, но это не имеет большого смысла, поскольку HardwareBuffer - это просто «тупой» кусок памяти с данными вершин в нем, а Mesh - это один или два HardwareBuffers с другими данными вокруг них, такие как формат вершин, материал и т. д. Наличие в них отдельных классов позволяет клиентскому коду иметь несколько сеток совместно с общим аппаратным буфером и тому подобное.

+0

Как класс реализации относится к IA? Получается ли это от этого? –

+0

Дизайн? Где? Какие альтернативы были рассмотрены? Почему их отвергли? –

+0

@VaughnCato Реализация IA происходит от IA –

ответ

1

Мне кажется, что это на самом деле неплохая идея с точки зрения дизайна.

Если вы используете интерфейсы (или имитируете их, так как C++ не имеет такой языковой структуры), вы используете их для публикации этих данных, которые необходимы в других местах. Поэтому, если объект, реализующий IB, должен сделать IA чем-то, чтобы восстановить его данные, это явно признак того, что либо IA публикует недостаточно данных, либо объект, реализующий IA, должен также реализовать другой более широкий интерфейс.

Трудно сказать, какой вариант лучше (или, может быть, если есть другой), потому что мы не знаем контекста здесь. Вообще литье следует избегать, если действительно не нужно, и это сурали - не нужно здесь.


Edit:

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 
} 

Таким образом, вы все равно сможете отрезать от реализации (например, вы сможете разрезать его на две независимые части), но внутри вашего движка все классы будут достаточно знать друг о друге, чтобы нормально работать.

+0

Ну, это в основном то, что я пытаюсь предоставить посредством этих абстрактных классов. Клиентский код знает, как использовать «IA» и «IB», и что «IB» берет указатель на «IA» и что-то делает. Какая реализация IA и IB, полученная клиентским кодом, продиктована фабричным объектом. Если я напишу еще одну версию IA и IB, которая использует другой API-интерфейс, клиентский код должен продолжать работать. –

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); 

Если вы всегда знать фактический динамический тип, не заходя в виртуальные таблицы (например, глобальный opengl флаг), то виртуальные таблицы, конечно, излишним. Вы можете написать всю программу с флагами и ветвями, заменяющими виртуальную отправку.

Двигатель должен обеспечивать тот же интерфейс независимо от того, какой API-интерфейс используется, поэтому я не думаю, что смог бы достичь этого с помощью виртуальных функций, потому что я не могу заранее знать, что нужно IB от IA ,

Как вы уже сказали, абстрактный базовый класс предоставляет интерфейс, а производный класс вызывает бэкэнд. Ваш пример немного отрывочный, но похоже, что IA и IB - это интерфейсы, которые вы должны определить независимым от вас способом, если вы собираетесь достичь своей цели ... независимо от реализации.

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-абстракция неглубокая. Тогда класс двигателя будет вырожден до простого форвардера. Хорошо.

+0

Я переосмысливаю интерфейсы, принимая во внимание то, что вы там сказали (вещь наследования). С утра уже слишком поздно: я вернусь к нему завтра. –

Смежные вопросы