10

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

我只想知道每种方法的优缺点。

微软方法的一个优点是,他们可以使用空类定义句柄的类型安全 层次结构,这(我认为)在其他两种方法中是不可能的,尽管我想知道这种层次结构会给实现带来什么好处?无论如何,还有什么?

编辑:

第二种方法(即使用不完整的类)的一个优点是我们可以防止客户端取消引用句柄(这意味着,这种方法似乎强烈支持封装,我想)。如果尝试取消引用句柄,代码甚至无法编译。还有什么?

第三种方法也具有相同的优势,即您不能取消引用句柄。

4

3 回答 3

3

方法 #1 介于 C 风格和 C++ 接口之间。您必须将句柄作为参数传递,而不是成员函数。暴露多态的优点是可以减少接口中的函数数量,并在编译时检查类型。通常大多数专家更喜欢 pimpl idiom(有时称为编译防火墙)而不是这样的接口。您不能使用方法#1 与 C 接口,因此最好使用完整的 C++。

方法 #2 是 C 风格的封装和信息隐藏。指针可能是(并且通常是)指向真实事物的指针,因此它没有过度设计。库的用户可能不会取消引用该指针。缺点是它不暴露任何多态性。优点是您可以在与用 C 编写的模块交互时使用它。

方法#3 是过度抽象的 C 风格封装。指针可能根本不是指针,因为库的用户不应该强制转换、解除分配或取消引用它。优点是它可能携带异常或错误值,缺点是它的大部分必须在运行时进行检查。

我同意 DeadMG 的观点,即语言中立的面向对象接口在 C++ 中使用起来非常容易和优雅,但是这些也涉及比编译时检查更多的运行时检查,并且当我不需要与其他语言交互时,它们就显得过大了。所以我个人更喜欢方法#2,如果它需要与 C 或 Pimpl 成语接口,而它只是 C++。

于 2010-12-24T18:43:08.660 回答
2

方法 3 根本不是很好,因为它允许混合和匹配实际上没有意义的句柄类型,任何接受 HANDLE 的函数都可以接受任何 HANDLE,即使编译时可确定这是错误的类型。

方法 1 的缺点是您必须在另一端对它们的实际类型进行大量转换。

方法 2 并没有那么糟糕,除了你不能用它做任何类型的继承,而不必每次都从外部查询。

然而,自从编译器发现如何实现高效的虚函数以来,所有这些都完全没有实际意义。DirectX 和 COM 采用的方法是最好的——它非常灵活、强大且完全类型安全。

它甚至允许一些真正疯狂的事情,比如您可以从 DirectX 接口继承并以这种方式扩展它。这样做的最大优势之一是 Direct2D 和 Direct3D11。它们实际上并不兼容(这确实非常愚蠢),但是您可以定义一个从 ID3D10Device1 继承并转发到 ID3D11Device 的代理类型并解决这样的问题。这种事情甚至永远不会考虑使用上述任何一种方法是可能的。

哦,最后一件事:你真的,真的不应该用全部大写来命名你的类型。

于 2010-12-24T12:54:35.017 回答
1

2 和 3 的类型安全性稍差,因为它们允许使用句柄而不是 void*

无效蓝屏(HBOOK hb){
  memset(hb,0,100000); // 没有编译错误
}
于 2010-12-24T12:37:05.670 回答