【问题标题】:struct vs class for writing D wrappers around foreign languagesstruct vs class 用于围绕外语编写 D 包装器
【发布时间】:2012-06-15 22:23:58
【问题描述】:

(注意:这与Usage preference between a struct and a class in D language 有关,但用于更具体的用例)

在为 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){}

【问题讨论】:

    标签: class struct wrapper d


    【解决方案1】:

    我建议你阅读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(在编译器的下一个版本 - dmd 2.060 中实际上将重命名为destroy)来销毁 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,并且垃圾收集器永远不会收集您的包装对象,则永远不会在 C++ 对象上调用deleteA。这可能很重要,也可能无关紧要。这取决于 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,但这肯定会更复杂。

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

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

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

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

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

    【讨论】:

    • 对于确定性销毁,您还可以将std.typecons.scoped 与类对象一起使用,这是即将弃用的scoped 语句的库替代方案。作用域意味着在创建类对象的作用域的出口处调用类的析构函数。顺便说一句,使用这样的接口是编写包装器的一种方法,但还有其他方法。您可以在没有太多限制的情况下公开 C 函数并实现跨语言多态性,并且可以将所有 C++ 对象收集到例如一些在应用退出时释放的全局散列和 C++ 析构函数将被调用。
    • 感谢您的详细回答,这很有帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-12
    相关资源
    最近更新 更多