(注意:这与Usage preference between a struct and a class in D language有关,但对于更具体的用例)
当将D接口写入C ++代码时,SWIG和其他人会这样做:
class A{
private _A*ptr;//defined as extern(C) elsewhere
this(){ptr=_A_new();}//ditto
this(string s){ptr=_A_new(s);} //ditto
~this(){_A_delete(ptr);} //ditto
void fun(){_A_fun(ptr);}
}
我们假设不需要继承。
我的问题是:为此更好地使用结构而不是类吗?
专业人士:
1)效率(堆栈分配)
2)易于使用(无需在任何地方写新内容,例如:auto a=A(B(1),C(2))
vs auto a=new A(new B(1),new C(2))
)?
缺点是: 需要额外的字段is_own来通过postblit来处理别名。
最好的方法是什么? 还有什么值得担心的吗? 这是一次尝试:
struct A{
private _A*ptr;
bool is_own;//required for postblit
static A opCall(){//cannot write this() for struct
A a;
a.ptr=_A_new();
a.is_own=true;
return a;
}
this(string s){ptr=_A_new(s); is_own=true;}
~this(){if(is_own) _A_delete(ptr);}
void fun(){_A_fun(ptr);}
this(this){//postblit;
//shallow copy: I don't want to call the C++ copy constructor (expensive or unknown semantics)
is_own=false; //to avoid _A_delete(ptr)
}
}
注意postblit对于调用以下函数的情况是必要的:
myfun(A a){}
答案 0 :(得分:4)
我建议您阅读this page。您可以在D中调用的C ++类上唯一的函数是虚函数。这意味着
D不能调用C ++特殊成员函数,反之亦然。这些包括构造函数,析构函数,转换运算符,运算符重载和分配器。
当您在D中声明C ++类时,使用extern(C++) interface
。所以,你的类/结构看起来像这样
extern(C++) interface A
{
void fun();
}
但是,您需要另一个extern(C++)
函数来分配A
类型的任何对象,因为它是必须执行此操作的C ++代码,因为D代码无法访问任何对象构造函数。您还需要一种方法将其传递回C ++代码,以便在完成后将其删除。
现在,如果你想将该接口包装在一个将要调用extern(C++)
函数来构造它的类型中,并使用extern(C++)
函数来删除它(这样你就不必担心手动执行此操作),然后是否使用类或结构完全取决于您尝试使用它。
一个类将是一个引用类型,它反映了C ++类实际上是什么。因此,传递它将无需你做任何特别的事情。但是如果你想要保证包装的C ++对象被释放,你必须手动完成,因为不能保证D类的终结器会被运行(并且可能是你放置代码的地方)调用C ++函数来删除C ++对象)。您必须使用clear
(在编译器的下一个版本中实际将其重命名为destroy
- dmd 2.060)来销毁D对象(即调用其终结器并处理销毁它的任何成员变量是值类型),或者你必须在调用C ++函数的D对象上调用一个函数来删除C ++对象。 e.g。
extern(C++) interface A
{
void fun();
}
extern(C++) A createA();
extern(C++) void deleteA(A a);
class Wrapper
{
public:
this()
{
_a = createA();
}
~this()
{
deleteA(_a);
}
auto opDispatch(string name, Args...)(Args args)
{
return mixin("_a." ~ name ~ "(args)");
}
private:
A _a;
}
void main()
{
auto wrapped = new Wrapper();
//do stuff...
//current
clear(wrapped);
//starting with dmd 2.060
//destroy(wrapped);
}
但这确实有一个缺点,如果你不调用clear
/ destroy
,并且垃圾收集器永远不会收集你的包装器对象,那么永远不会在C ++上调用deleteA
宾语。这可能或不重要。它取决于C ++对象是否真的需要在程序终止之前调用它的析构函数,或者它是否可以让它的内存返回到OS(没有调用析构函数),如果GC从不需要收集包装程序终止对象
如果你想要确定性破坏,那么你需要一个结构。这意味着您需要担心将结构化为引用类型。否则,如果它被复制,当其中一个被销毁时,C ++对象将被删除,另一个结构将指向垃圾(然后它会在被销毁时尝试并删除)。要解决此问题,您可以使用std.typecons.RefCounted
。然后你得到类似
extern(C++) interface A
{
void fun();
}
extern(C++) A createA();
extern(C++) void deleteA(A a);
struct Wrapper
{
public:
static Wrapper opCall()
{
Wrapper retval;
retval._a = createA();
return retval;
}
~this()
{
if(_a !is null)
{
deleteA(_a);
_a = null;
}
}
auto opDispatch(string name, Args...)(Args args)
{
return mixin("_a." ~ name ~ "(args)");
}
private:
A _a;
}
void main()
{
auto wrapped = RefCounted!Wrapper();
//do stuff...
}
您还可以定义包装器,使其中包含引用计数逻辑并避免使用RefCounted
,但这肯定会更复杂。
无论如何,我肯定会反对你使用bool
标记包装器是否拥有C ++对象的建议,因为如果原始包装器对象在所有副本之前被销毁,那么你的副本将指向垃圾。
如果 希望使用C ++对象的复制构造函数(并因此将C ++对象视为值类型),则另一个选项是添加extern(C++)
函数, C ++对象并返回它的副本,然后在postblit中使用它。
extern(C++) A copyA(A a);
this(this)
{
if(_a !is null)
_a = copyA(a);
}
希望这能使事情变得清晰。