Сравнение дескрипторов: пустые классы против неопределенных классов против void * - PullRequest
9 голосов
/ 24 декабря 2010

Microsoft GDI + определяет множество пустых классов, которые должны обрабатываться как внутренние.Например, (источник GdiPlusGpStubs.h)

//Approach 1

class GpGraphics {};

class GpBrush {};
class GpTexture : public GpBrush {};
class GpSolidFill : public GpBrush {};
class GpLineGradient : public GpBrush {};
class GpPathGradient : public GpBrush {};
class GpHatch : public GpBrush {};

class GpPen {};
class GpCustomLineCap {};

Есть два других способа определения дескрипторов.Они,

//Approach 2
class BOOK;  //no need to define it!
typedef BOOK *PBOOK;
typedef PBOOK HBOOK; //handle to be used internally

//Approach 3
typedef void* PVOID;
typedef PVOID HBOOK; //handle to be used internally

Я просто хочу знать преимущества и недостатки каждого из этих подходов.

Одно из преимуществ подхода Microsoft заключается в том, что они могут определять type-безопасная иерархия дескрипторов с использованием пустых классов, что (я думаю) невозможно при двух других подходах, хотя мне интересно, какие преимущества эта иерархия принесет реализации?В любом случае, что еще?

РЕДАКТИРОВАТЬ:

Одним из преимуществ второго подхода (т. Е. Использования неполных классов) является то, что мы можем предотвратить разыменование дескрипторов клиентами (это означает, что этот подход поддерживаетинкапсуляция сильно, я полагаю).Код даже не скомпилируется, если попытаться разыменовать дескрипторы.Что еще?

То же преимущество, что и в третьем подходе, в том, что вы не можете разыменовать ручки.

Ответы [ 3 ]

2 голосов
/ 24 декабря 2010

Подход 3 не очень хорош, так как он позволяет смешивать и сопоставлять типы дескрипторов, которые на самом деле не имеют смысла, любая функция, которая принимает HANDLE, может принимать любую HANDLE, даже если она определяется во время компиляции, что этонеправильный тип.

Недостаток подхода 1 заключается в том, что вы должны выполнить наложение на другом конце их фактических типов.

Подход 2 не так уж и плох, кромеВы не можете выполнять какие-либо виды наследования без необходимости каждый раз выполнять внешний запрос.

Однако все это не имеет смысла с тех пор, как компиляторы обнаружили, как реализовать эффективные виртуальные функции.Подход, принятый DirectX и COM, является лучшим - он очень гибкий, мощный и полностью безопасный для типов.

Он даже допускает некоторые действительно безумные вещи, например, вы можете наследовать от интерфейсов DirectX и расширять его таким образом.,Одним из лучших преимуществ этого является Direct2D и Direct3D11.На самом деле они несовместимы (что на самом деле ужасно глупо), но вы можете определить тип прокси, который наследуется от ID3D10Device1 и перенаправить на ID3D11Device, и решить эту проблему таким образом.Такого рода вещи никогда бы даже не подумали о возможности при любом из вышеперечисленных подходов.

Да, и последнее: вам действительно не следует называть ваши типы во всех заглавных буквах.

2 голосов
/ 24 декабря 2010

Подход № 1 - это нечто среднее между стилем C и интерфейсом C ++.Вместо функций-членов вы должны передать дескриптор в качестве аргумента.Преимущество открытого полиморфизма в том, что вы можете уменьшить количество функций в интерфейсе, а типы проверяются во время компиляции.Обычно большинство экспертов предпочитают идиому pimpl (иногда называемую firewall компиляции) для такого интерфейса.Вы не можете использовать подход № 1 для взаимодействия с C, поэтому лучше переходите на полный C ++.

Подход # 2 - это инкапсуляция в стиле C и сокрытие информации.Указатель может быть (и часто является) указателем на реальную вещь, поэтому он не перегружен.Пользователь библиотеки не может разыменовать этот указатель.Недостатком является то, что он не подвергает полиморфизму.Преимущество заключается в том, что вы можете использовать его при взаимодействии с модулями, написанными на C.

Подход № 3 - это чрезмерно абстрактная инкапсуляция в стиле C.Указатель может быть вовсе не указателем, так как пользователь библиотеки не должен его приводить, освобождать или разыменовывать.Преимущество заключается в том, что он может нести значения исключений или ошибок, а недостатком является то, что большую часть его нужно проверять во время выполнения.

Я согласен с DeadMG, что не зависящие от языка объектно-ориентированные интерфейсы очень просты и элегантны в использованиииз C ++, но они также включают больше проверок во время выполнения, чем проверки во время компиляции, и излишни, когда мне не нужно взаимодействовать с другими языками.Так что я лично предпочитаю Подход № 2, если он должен взаимодействовать с идиомой C или Pimpl, когда это только C ++.

1 голос
/ 24 декабря 2010

2 и 3 немного менее безопасны, поскольку позволяют использовать ручки вместо void *

void bluescreeen(HBOOK hb){
  memset(hb,0,100000); // no compile errors
}
...