5

我正在考虑用 c++ 包装一些 c 库,但我不确定包装不透明指针的最佳方法是什么。

当 C 结构是公共 API 的一部分时

typedef struct _SomeType
{
    int a;
    int b;
} SomeType_t;

有几个“成员”功能的地方:

void SomeTypeFoo( SomeType_t* obj, ... );
void SomeTypeBar( SomeType_t* obj, ... );

我喜欢从基础派生的方法,将这些“成员”函数简单地关联为实际的类成员。IE:

class SomeTypeWrapper:
    public SomeType_t
{
    void foo( ... );
    void bar( ... );
};

就我的理解而言,我相信这SomeTypeWrapper是“二进制兼容的” struct _SomeTypeSomeTypeWrapper*可以强制转换为,SomeType_t*以便 c++ 方法实现可能是:

SomeTypeWrapper::foo( ... )
{
    SomeTypeFoo( (SomeType_t*)this, ... );
}

见注[1]。

但是,在某些库中,他们喜欢将结构的定义保持为私有(我想这是为了允许在不更改 headers/API 的情况下更改实现)。所以在标题中我有这样的东西:

typedef struct _SomeType SomeType_t;

然后所有的“成员”函数都处理这些不透明的指针。为了将这些方法“包装”到一个接口中,到目前为止我所做的是提供一个包含指针的类。

class SomeTypeWrapper
{
    private:
        SomeType_t* m_data;

    public:
        SomeTypeWrapper( SomeType_t* data ): m_data(data){}
        void foo(...);
        void bar(...);
};

这意味着对于 c 库中返回这种不透明指针的任何函数,我必须分配一个新对象来携带该指针。对于许多可能很好但这种设计使包装器实现复杂化的库以及执行大量小对象分配的快速 c 库,我怀疑这种额外的对象开销会导致显着的性能损失。此外,底层对象可能是引用计数的,所以在某些情况下,我必须决定 SomeWrapperType 是否增加引用计数、实现复制构造函数、是否具有私有构造函数等。

在大多数情况下,我很乐意为更好的界面付出代价,但我正在寻找其他选择。有没有更简洁的方法来处理 c++ 包装库中的不透明 c 指针?相关:是否有一种实现包装器类的好方法,以便 c++ 标头不必包含 c-library 标头并且不需要包装器对象分配?

注意[1]:我相信这个转换是安全的,只要使用相同的编译器并且SomeTypeWrapper不添加额外的成员并且没有虚拟方法。通过相同的编译器,我假设由 gcc 编译的 c 库将与由相同 gcc 编译的 c++ 包装器一起使用。也许这不能保证?

4

3 回答 3

5

您(问题中)的最终版本SomeTypeWrapper是前进的方向。

这意味着对于 c 库中返回这种不透明指针的任何函数,我必须分配一个新对象来携带该指针。对于许多可能很好但这种设计使包装器实现复杂化的库以及执行大量小对象分配的快速 c 库,我怀疑这种额外的对象开销会导致显着的性能损失。

没有开销。class具有一个数据成员且没有方法的 a 实例virtual不大于其唯一成员。如果您在标头中内联定义方法,则也不会有函数调用开销。因此,只要您不使用 , 分配这些实例new,而是在堆栈上:

SomeTypeWrapper some_object(make_some_object());  // one allocation on the heap

那么你有一个零开销的解决方案。

于 2012-07-20T20:26:17.163 回答
3

如果你包装你的不透明指针,你肯定不能从它派生。一种看似有吸引力的免费获得某些功能的方法,当您在打算聚合的地方使用继承时,它很快就会变得混乱:您的包装器不是库的对象之一,相反,它...包装了一个库对象

如果您像对待库对象一样小心对待包装器对象,那么每个不透明指针总是有一个包装器(最多)。您可以与例如 a 共享您的包装器对象unique_ptr(C++11 拥有它们!)。这是一件好事,考虑到包装的对象是一种宝贵的资源。

说到共享指针,它们可以将deleter函数作为构造函数参数:

struct Wrapper : boost::non_copyable 
{
private:
   std::unique_ptr<SomeType> wrapped;
public:
   Wrapper()
   :wrapped(SomeType_create(), &SomeType_destroy)
   // upon destruction of wrapped, use SomeType_destroy
   {
   }

   void foo() { SomeType_foo(wrapped.get()); }
   int bar(int i) { return SomeType_bar(wrapped.get(),i); }
};

是的:您需要为 ; 中的每个成员函数提供一个方法包装器SomeType。这是完全正常的,因为 C++ 使用与 C 不同的调用约定,并且您必须用 C++ 替换每个 C 样式的成员函数。

而且您必须注意为每个库对象创建一个包装器;此对象在您的控制之下,您可以与您信任的任何人共享它,但它只是一个对象。

于 2012-07-22T10:43:48.930 回答
0

通常,您在上面提出的建议是一种丑陋的黑客攻击,可能会导致各种麻烦。可以向上(或向下,当它有意义时)转换继承层次结构,但是当从指向包装类的指针转换为指向第一个成员指向的对象的指针时,它是未定义的)。绝对不能保证第一个成员的地址将与您的包装器地址对齐。

在我看来,你在这里试图解决一个错误的问题。也许以不正确的策略为每种小型 C“对象”创建 C++ 包装器?为什么不拥有一个包装底层 C 库功能的库,而不是包装单个对象?当它这样做时,它以一种有意义的、简约的方式管理底层的 C 对象(即通过使用 RIAA、共享指针等)

于 2012-07-20T20:22:13.810 回答