struct vs class用于编写围绕外语的D包装器

时间:2012-06-15 22:23:59

标签: class struct wrapper d

(注意:这与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){}

1 个答案:

答案 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);
}

希望这能使事情变得清晰。