2

(注意:这与D 语言中结构和类之间的使用偏好有关,但针对更具体的用例)

在为 C++ 代码编写 D 接口时,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){}
4

1 回答 1

4

我建议你阅读这个页面。您可以在 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++)函数来删除它(这样您就不必担心手动执行此操作),那么您是否使用类或 struct 完全取决于你想用它做什么。

一个类将是一个引用类型,它反映了 C++ 类的实际情况。因此,无需您做任何特别的事情,就可以传递它。但是,如果您想要保证包装的 C++ 对象已被释放,则必须手动执行此操作,因为无法保证 D 类的终结器将永远运行(并且大概是您放置代码的地方)调用 C++ 函数来删除 C++ 对象)。您必须使用clear(实际上将destroy在编译器的下一个版本中重命名为 - dmd 2.060)来销毁 D 对象(即调用其终结器并处理其任何值类型的成员变量的销毁) ,否则您必须调用 D 对象上的一个函数,该函数调用 C++ 函数来删除 C++ 对象。例如

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,并且垃圾收集器永远不会收集您的包装器对象,deleteA则永远不会在 C++ 对象上调用。这可能很重要,也可能无关紧要。这取决于 C++ 对象是否真的需要在程序终止之前调用它的析构函数,或者如果 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,但这肯定会更复杂。

无论如何,我绝对不建议您使用 abool来标记包装器是否拥有 C++ 对象的建议,因为如果原始包装器对象在所有副本之前被销毁,那么您的副本将指向垃圾。

如果您确实希望使用 C++ 对象的复制构造函数(因此将 C++ 对象视为值类型),则另一个选择是添加一个extern(C++)函数,该函数采用 C++ 对象并返回它的副本,然后在 postblit 中使用它.

extern(C++) A copyA(A a);

this(this)
{
    if(_a !is null)
        _a = copyA(a);
}

希望这能让事情变得足够清楚。

于 2012-06-16T02:14:58.377 回答