Должен ли я использовать целочисленный идентификатор или указатели для моих непрозрачных объектов? - PullRequest
5 голосов
/ 30 ноября 2011

Я пишу слой абстракции поверх некоторого графического API (DirectX9 и DirectX11), и мне хотелось бы узнать ваше мнение.

Традиционно я бы создавал базовый класс для каждой концепции, которую я хочу абстрагировать.
Таким образом, в типичной ОО-моде у меня есть, например, класс Shader и 2 подкласса DX9Shader и DX11Shader.

Я бы повторил процесс для текстур и т. Д. ... и когда мне нужно создать их экземпляр, у меня есть абстрактная фабрика, которая будет возвращать соответствующий подкласс в зависимости от текущего графического API.
После RAII возвращенный указатель будет инкапсулирован в std :: shared_ptr.

Пока все хорошо, но в моем случае есть несколько проблем с этим подходом:

  1. Мне нужно придумать открытый интерфейс, который инкапсулирует функциональность обоих API (и других API в будущем).
  2. Производный класс хранится в отдельных библиотеках DLL (одна для DX9, одна для DX11 и т. Д.), И наличие для них shared_ptr в клиенте является проклятием: при выходе графические библиотеки выгружаются, а если клиент все еще имеет shared_ptr для одного из графических объектов boom , сбой из-за вызова кода из незагруженной DLL.

Это побудило меня переделать способ, которым я делаю вещи: Я думал, что смогу просто вернуть необработанные указатели на ресурсы и иметь графический интерфейс API чистым после себя, но все еще остается проблема висячих указателей на стороне клиента и проблемы интерфейса. Я даже считал ручной подсчет ссылок похожим на COM, но подумал, что это будет шагом назад (поправьте меня, если я ошибаюсь, если исходить из мира shared_ptr, ручной подсчет ссылок кажется примитивным).

Затем я увидел работу Хумуса, где все его графические классы представлены целочисленными идентификаторами (очень похоже на то, что делает OpenGL). Создание нового объекта возвращает только его целочисленный идентификатор и сохраняет указатель внутри; это все совершенно непрозрачно!

Классы, представляющие абстракцию (например, DX9Shader и т. Д.), Все скрыты за API устройства, который является единственным интерфейсом.
Если кто-то хочет установить текстуру, это просто вопрос вызова устройства-> SetTexture (ID), а все остальное происходит за кулисами.

Недостатком является то, что скрытая часть API является раздутой, для работы системы требуется много кода, и я не являюсь поклонником класса "сделай все".

Есть идеи / мысли?

Ответы [ 3 ]

3 голосов
/ 30 ноября 2011

Вы говорите, что основная проблема заключается в том, что DLL выгружается, все еще имея указатель на свои внутренние компоненты.Ну ... не делай этого .У вас есть экземпляр класса, члены которого реализованы в этой DLL.По сути, это ошибка для этой DLL, которая должна быть выгружена, пока существуют эти экземпляры классов.

Поэтому вы должны нести ответственность за то, как вы используете эту абстракцию.Точно так же, как вы должны нести ответственность за любой код, который вы загружаете из DLL: все, что исходит из DLL, должно быть очищено перед выгрузкой DLL.Как вы это делаете, зависит от вас.Вы можете иметь внутренний счетчик ссылок, который увеличивается для каждого объекта, который возвращает DLL, и выгружает DLL только после того, как все объекты, на которые есть ссылки, исчезнут.Или что-нибудь, действительно.

В конце концов, даже если вы используете эти непрозрачные числа или что-то еще, что произойдет, если вы вызовете одну из этих функций API для этого номера, когда DLL выгружается?Упс ... Так что это не дает тебе никакой защиты.Вы должны нести ответственность в любом случае.

Недостатки числового метода, о котором вы, возможно, не думаете:

  • Ограниченная способность знать, что на самом деле является объектом есть .Вызовы API могут потерпеть неудачу, потому что вы передали номер, который на самом деле не является объектом.Или хуже, что произойдет, если вы передадите шейдерный объект в функцию, которая принимает текстуру?Может быть, мы говорим о функции, которая принимает шейдер и текстуру, и вы случайно забыли порядок аргументов?Правила C ++ не позволяют этому коду даже компилироваться, если бы это были указатели на объекты.Но с целыми числами?Все хорошо;вы получите только ошибки времени выполнения.

  • Производительность.Каждый вызов API должен искать этот номер в хеш-таблице или что-то еще, чтобы получить фактический указатель для работы.Если это хеш-таблица (т. Е. Массив), то она, вероятно, довольно незначительна.Но это все-таки косвенность.А поскольку ваша абстракция кажется очень низкоуровневой, любая потеря производительности на этом уровне может действительно повредить в критических ситуациях.

  • Отсутствие RAII и других механизмов определения объема.Конечно, вы можете написать shared_ptr -esque объект, который будет создавать и удалять их.Но вам бы не пришлось этого делать, если бы вы использовали реальный указатель.

Это просто не выглядит полезным.

3 голосов
/ 30 ноября 2011

Имеет ли это значение? Для пользователя объекта это просто непрозрачный дескриптор. его фактический тип реализации не имеет значения, пока я могу передать дескриптор вашим функциям API и заставить их что-то делать с объектом.

Вы можете легко изменить реализацию этих дескрипторов, поэтому сделайте все, что вам будет проще сейчас .

Просто объявите тип дескриптора как typedef для указателя или целого числа и убедитесь, что весь код клиента использует имя typedef, тогда код клиента не зависит от конкретного типа, который вы выбрали для представления своих дескрипторов.

Выберите простое решение сейчас, и если / когда вы столкнетесь с проблемами, потому что это было слишком просто, измените его.

0 голосов
/ 30 ноября 2011

Относительно вашего р.2: Клиент всегда выгружается перед библиотеками.

Каждый процесс имеет свое дерево зависимостей библиотек, с .exe в качестве корневого дерева, пользователем Dll на промежуточных уровнях и системными библиотеками на низком уровне.Процесс загружается с низкого на высокий уровень, корень дерева (exe) загружается последним.Процесс выгружается начиная с корня, низкоуровневые библиотеки выгружаются последними.Это сделано для предотвращения ситуаций, о которых вы говорите.

Конечно, если вы загружаете / выгружаете библиотеки вручную, этот порядок меняется, и вы несете ответственность за поддержание указателей в силе.

...