为什么对象句柄经常显示为指向指针的指针

why handle to an object frequently appears as pointer-to-pointer

将对象的句柄设置为指向指针而不是指针的目的是什么?喜欢下面的代码:

FT_Library library;
FT_Error error = FT_Init_FreeType( &library );

哪里

typedef struct FT_LibraryRec_  *FT_Library

所以 &libraryFT_LIBraryRec_ 类型 FT_LIBraryRec_**

的句柄

这是一种在 C 中模拟 通过引用传递的方法,否则只能通过值传递。

'C'库函数FT_Init_FreeType有两个输出,错误代码and/or库句柄(指针)。

在 C++ 中,我们更自然地选择:

  • return 封装了调用成功或失败和库句柄的对象,或者

  • return 一个输出 - 库句柄,并在失败时抛出异常。

C API 通常不会以这种方式实现。

C 库函数 return 成功代码并传递 in/out 变量的地址以进行有条件变异的情况并不少见,如上述情况。

该方法隐藏了实现。它加快了代码的编译速度。它允许在不破坏使用它们的现有代码的情况下升级库使用的数据结构。最后,它确保该对象的地址永远不会改变,并且您不会复制这些对象。

以下是单指针版本的实现方式:

struct FT_Struct
{
    // Some fields/properties go here, e.g.
    int field1;
    char* field2;
}
FT_Error Init( FT_Struct* p )
{
    p->field1 = 11;
    p->field2 = malloc( 100 );
    if( nullptr == p->field2 )
        return E_OUTOFMEMORY;
    return S_OK;
}

或 C++ 等价物,没有任何指针:

class FT_Struct
{
    int field1;
    std::vector<char> field2;
public:
    FT_Struct() :
        field1( 11 )
    {
        field2.resize( 100 );
    }
};
  1. 作为库的用户,您必须包含 struct/class FT_Struct 定义。库可能非常复杂,因此这会减慢代码的编译速度。
  2. 如果库是动态的,即 windows 上的 *.dll、linux 上的 *.so 或 osx 上的 *.dylib,则升级库,如果新版本更改 struct/class 的内存布局,旧应用程序将崩溃。
  3. 由于 C++ 的工作方式,对象按值传递,即您通常希望它们是可移动和可复制的,这不一定是库作者想要支持的。

现在考虑以下函数:

FT_Error Init( FT_Struct** pp )
{
    try
    {
        *pp = new FT_Struct();
        return S_OK;
    }
    catch( std::exception& ex )
    {
        return E_FAIL;
    }
}

作为图书馆的用户,您不再需要知道里面有什么 FT_Struct,甚至不需要知道它有多大。您不需要 #include 实现细节,即编译会更快。 这与动态库配合得很好,库作者可以随意更改内存布局,只要 C API 稳定,旧应用程序将继续工作。 API 保证你不会复制或移动值,你不能复制未知长度的结构。