我有一个C库,其类型如下:
typedef struct {
// ...
} mytype;
mytype *mytype_new() {
mytype *t = malloc(sizeof(*t));
// [Initialize t]
return t;
}
void mytype_dosomething(mytype *t, int arg);
我想提供C ++包装器来提供更好的语法。但是,我想避免使用单独分配的包装器对象的复杂性。我有一个相对复杂的对象图,其内存管理已经比我想要的更复杂(对象以所有可到达对象保持活动的方式被重新计算)。此外,C库将使用指向此对象的指针回调到C ++,并且为每个C-> C ++回调构建一个新的包装器对象的成本(因为C不知道包装器)对我来说是不可接受的。
我的一般方案是:
class MyType : public mytype {
public:
static MyType* New() { return (MyType*)mytype_new(); }
void DoSomething(int arg) { mytype_dosomething(this, arg); }
};
这将为C ++程序员提供更好的语法:
// C Usage:
mytype *t = mytype_new();
mytype_dosomething(t, arg);
// C++ Usage:
MyType *t = MyType::New();
t->DoSomething(arg);
我认为我正在向mytype*
转发一个malloc()
(用MyType*
分配),这是一个谎言。但是如果MyType
没有成员而没有虚函数,那么我似乎应该能够依赖sizeof(mytype) == sizeof(MyType)
,除了MyType
之外没有编译器可以生成任何类型的实际数据参考。
所以尽管这可能违反了C ++标准,但我很想我可以通过各种编译器和平台来解决这个问题。
我的问题是:
class MyType {
public:
MyType() { mytype_init(this); }
private:
mytype t;
};
原因是我有C回调,它将使用mytype*
回调到C ++,我希望能够将其直接转换为MyType*
而无需复制。
答案 0 :(得分:5)
您将mytype*
向下转换为MyType*
,这是合法的C ++。但是这里存在问题,因为mytype*
指针实际上并不指向MyType
。它实际上指向mytype
。因此,如果你贬低它做一个MyType
并尝试访问其成员,它几乎肯定不会工作。即使没有数据成员或virtual
函数,您将来也可能使用它,但它仍然是一个巨大的代码味道。
即使它没有违反C ++标准(我认为它也是如此),我仍然会对代码有点怀疑。通常,如果您正在包装C库,那么“现代C ++方式”就是通过RAII idiom:
class MyType
{
public:
// Constructor
MyType() : myType(::mytype_new()) {}
// Destructor
~MyType() { ::my_type_delete(); /* or something similar to this */ }
mytype* GetRawMyType() { return myType; }
const mytype* GetConstRawMyType() const { return myType; }
void DoSomething(int arg) { ::mytype_dosomething(myType, int arg); }
private:
// MyType is not copyable.
MyType(const MyType&);
MyType& operator=(const MyType&);
mytype* myType;
};
// Usage example:
{
MyType t; // constructor called here
t.DoSomething(123);
} // destructor called when scope ends
答案 1 :(得分:3)
我认为拥有mytype*
的{{1}}数据成员会更加安全和优雅,并在MyType
的构造函数中初始化它而不是MyType
方法(顺便说一句,如果你想拥有它,必须是静态的)。
答案 2 :(得分:3)
通过一些好运,这有可能实际上并没有违反C ++标准吗?
我不是在提倡这种风格,但由于MyType
和mytype
都是POD,我相信演员表并没有违反标准。我相信MyType
和mytype
是布局兼容的(2003版,第9.2节,第14节:“如果两个POD-struct ...类型具有相同数量的非静态数据成员,则它们是布局兼容的和相应的非静态数据成员(按顺序)具有布局兼容类型(3.9)。“),因此可以毫无困难地进行转换。
编辑:我不得不测试一下,事实证明我错了。这不是标准的,因为基类使{P}成为非POD。以下内容无法编译:
MyType
Visual C ++提供错误消息“具有基础的类型不是聚合的”。由于“带有基础的类型不是聚合的”,因此我引用的段落根本不适用。
我相信您仍然安全,因为大多数编译器会使#include <cstdio>
namespace {
extern "C" struct Foo {
int i;
};
extern "C" int do_foo(Foo* f)
{
return 5 + f->i;
}
struct Bar : Foo {
int foo_it_up()
{
return do_foo(this);
}
};
}
int main()
{
Bar f = { 5 };
std::printf("%d\n", f.foo_it_up());
}
布局与MyType
兼容。演员将“工作”,但这不是标准。
答案 3 :(得分:-1)
它确实违反了c ++标准,但它应该适用于大多数(我所知道的)编译器 你在这里依赖于一个特定的实现细节(编译器不关心实际对象是什么,只是你给它的类型),但我认为任何编译器都没有不同的实现细节。一定要在你使用的每个编译器上检查它,它可能会让你措手不及。