句柄比较:空类、未定义类、void*
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's GDI+ defines many empty classes to be treated as handles internally. For example, (source 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 {};
There are other two ways to define handles. They're,
//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
I just want to know the advantages and disadvantages of each of these approaches.
One advantage with Microsoft's approach is that, they can define type-safe hierarchy of handles using empty classes, which (I think) is not possible with the other two approaches, though I wonder what advantages this hierarchy would bring to the implementation? Anyway, what else?
EDIT:
One advantage with the second approach (i.e using incomplete classes) is that we can prevent clients from dereferencing the handles (that means, this approach appears to support encapsulation strongly, I suppose). The code would not even compile if one attempts to dereference handles. What else?
The same advantage one has with third approach as well, that you cannot dereference the handles.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
方法 #1 是 C 风格和 C++ 接口之间的某种中间方式。您必须将句柄作为参数传递,而不是成员函数。暴露多态性的优点是可以减少接口中函数的数量,并在编译时检查类型。通常大多数专家更喜欢 pimpl idiom(有时称为编译防火墙)而不是这样的接口。您不能使用方法 #1 与 C 交互,因此最好使用完整的 C++。
方法#2 是C 风格封装和信息隐藏。该指针可能(并且通常是)指向真实事物的指针,因此它没有被过度设计。库的用户不能取消引用该指针。缺点是它没有暴露任何多态性。优点是您可以在与用 C 编写的模块交互时使用它。
方法 #3 是过度抽象的 C 风格封装。该指针可能实际上根本不是指针,因为库的用户不应该强制转换、释放或取消引用它。优点是它可能会携带异常或错误值,缺点是大部分必须在运行时进行检查。
我同意 DeadMG 的观点,即语言中立的面向对象的接口在 C++ 中使用起来非常简单和优雅,但这些也涉及比编译时检查更多的运行时检查,并且当我不需要与其他语言交互时就太过分了。所以我个人更喜欢方法 #2,如果它需要与 C 或 Pimpl 习惯用法交互,而它只是 C++。
Approach #1 is some mid-way between C style and C++ interface. Instead of member functions you have to pass the handle as argument. The advantage of exposed polymorphism is that you can reduce the amount of functions in interface and the types are checked compile time. Usually most experts prefer pimpl idiom (sometimes called compilation firewall) to such interface. You can not use approach #1 to interface with C so better go full C++.
Approach #2 is C style encapsulation and information hiding. The pointer may be (and often is) a pointer to real thing, so it is not over-engineered. User of library may not dereference that pointer. Disadvantage is that it does not expose any polymorphism. Advantage is that you may use it when interfacing with modules written in C.
Approach #3 is over-abstracted C-style encapsulation. The pointer may be really not a pointer at all since user of library should not cast, deallocate or dereference it. Advantage is that it may so carry exception or error values, disadvantage is that most of it has to be checked run time.
I agree with DeadMG that language-neutral object-oriented interfaces are very easy and elegant to use from C++, but these also involve more run-time checks than compile time checks and are overkill when i don't need to interface with other languages. So i personally prefer Approach #2 if it needs to interface with C or Pimpl idiom when it is C++ only.
方法 3 根本不是很好,因为它允许混合和匹配实际上没有意义的句柄类型,任何采用 HANDLE 的函数都可以采用任何 HANDLE,即使编译时可确定这是错误的类型。
方法 1 的缺点是您必须在另一端对其实际类型进行大量转换。
方法 2 并没有那么糟糕,只是您不能使用它进行任何类型的继承,而不必每次都进行外部查询。
然而,自从编译器发现如何实现高效的虚函数以来,所有这些都完全没有意义了。 DirectX 和 COM 所采用的方法是最好的 - 它非常灵活、强大且完全类型安全。
它甚至允许一些真正疯狂的事情,比如您可以继承 DirectX 接口并以这种方式扩展它。其最大优点之一是 Direct2D 和 Direct3D11。它们实际上并不兼容(这确实是非常愚蠢的),但是您可以定义一个继承自 ID3D10Device1 并转发到 ID3D11Device 的代理类型并解决这样的问题。这种事情根本就不会考虑通过上述任何一种方法来实现。
哦,最后一件事:你真的、真的不应该用全大写来命名你的类型。
Approach 3 is not very good at all, as it allows the mixing and matching of handle types that don't actually make sense, any function that takes a HANDLE can take any HANDLE, even if it's compile-time determinable that that is the wrong type.
The downside of Approach 1 is that you have to do a bunch of casting on the other end to their actual types.
Approach 2 isn't that bad, except you can't do any kind of inheritance with it without having to externally query every time.
However, all of this is entirely moot ever since compilers discovered how to implement efficient virtual functions. The approach taken by DirectX and COM is the best- it's very flexible, powerful, and completely type-safe.
It even allows for some truly insane things, like you can inherit from DirectX interfaces and extend it that way. One of the best advantages of this is Direct2D and Direct3D11. They're not actually compatible (which is truly, horrendously stupid), but you can define a proxy type that inherits from ID3D10Device1 and forwards to the ID3D11Device and solve the problem like that. That kind of thing would never even think about being possible with any of the above approaches.
Oh, and last thing: You really, really shouldn't name your types in allcaps.
2 和 3 的类型安全性稍差,因为它们允许使用句柄而不是 void*
2 and 3 are slightly less typesafe as they allow to use handles instead of void*