【问题标题】:Calling a function at object deletion in Haskell在 Haskell 中删除对象时调用函数
【发布时间】:2015-08-31 14:05:11
【问题描述】:

我正在为 C++ 类编写 Haskell 包装器。我决定将其表示为包含指向 C++ 中类实例的指针 (Foreign.Ptr) 的 Haskell 数据结构。类似的东西。

在 C++ 中:

class MyClass {
public:
    double my_method();

    // ...
};

extern "C" MyClass* cpp_new_MyClass() {
    return new MyClass();
}

extern "C" double cpp_my_method(MyClass *obj) {
    return obj->my_method();
}

在 Haskell 中:

Data MyClass = MyClass (Ptr ())

foreign import ccall "cpp_new_MyClass" cppNewMyClass :: Ptr () 
foreign import ccall "cpp_my_method" cppMyMethod :: Ptr () -> Double

mkMyClass :: MyClass
mkMyClass = MyClass cppNewMyClass

myMethod :: MyClass -> Double
myMethod (MyClass ptr) = cppMyMethod ptr

问题是,我不知道如何正确实现 MyClass 删除。在某些时候,Haskell 垃圾收集器会删除 MyClass 对象,但不会触发 C++ 中的 MyClass* 内存释放。我该如何解决?

我知道ForeignPtr,但它使用IO monad,这并不令人满意,因为我希望包装的数据结构与普通的 Haskell 数据结构完全一样,而不需要显式分配/释放内存或IO monads。

【问题讨论】:

  • 你的课堂副作用免费吗?如果不是,因为它在 C++ 中很常见,您必须 使用IO monad,或者小心 确保向它公开一个纯接口。或者失去引用透明度,这在 Haskell 中是一个致命的罪过,因为它会使程序的行为几乎无法预测,尤其是当优化器假设它重写你的代码时。
  • @chi 是的,没有副作用。

标签: c++ haskell garbage-collection ffi


【解决方案1】:

“它使用IO monad,这并不令人满意,因为我希望包装的数据结构与普通的 Haskell 数据结构完全一样”

当然可以,但不幸的是,这实际上是不可能的。外来的“函数”总是可以做一些在 Haskell 中不可能做的有趣的事情;类型系统无法查看并阻止它。

这种困境是我们拥有unsafePerformIO 的唯一 (!) 原因,而您的确实是该事物有效应用的一个很好的例子。

我自己还没有这样做,但是您的代码应该如下所示:

extern "C" void cpp_delete_MyClass(MyClass* obj) {
    delete obj;
}
foreign import ccall "cpp_new_MyClass" cppNewMyClass :: IO (Ptr ())
foreign import ccall "&cpp_delete_MyClass" cppDeleteMyClass :: FunPtr (Ptr () -> IO ())

data MyClass = MyClass (ForeignPtr ())

mkMyClass :: MyClass
mkMyClass = unsafePerformIO $ do
   newObj <- cppNewMyClass
   fPtr <- newForeignPtr cppDeleteMyClass newObj
   return $ MyClass fptr

我不太确定那些FunPtrs,希望有人对此发表评论......

【讨论】:

  • 我想到了在ForeignPtr 上使用unsafePerformIO 的这种解决方案,但我不确定它是否能正常工作。如果是,您能否解释一下这种方法的幕后情况?
  • @wrwt 每次评估mkMyClass 时(应该只计算一次,但不要依赖它)分配一个 C++ 对象,并创建一个带有终结器的 Haskell 值,并且作为纯值返回。当其中一个值被垃圾回收时,终结器 (cppDeleteMyClass) 将运行。
  • 关于FunPtrs。声明cppDeleteMyClass 的正确方法是foreign import ccall "&amp;cpp_delete_MyClass" cppDeleteMyClass :: FunPtr (Ptr () -&gt; IO ())(添加了&)。你可能应该修正你的答案。
  • 如果你想保证cppNewMyClass 只会被调用一次,你应该在mkMyClass 上加上一个NOINLINE(虽然听起来在这种情况下OP 可能不需要这样的保证)。
猜你喜欢
  • 2013-09-30
  • 2014-12-09
  • 2019-10-08
  • 1970-01-01
  • 2021-04-10
  • 1970-01-01
  • 1970-01-01
  • 2021-04-12
  • 1970-01-01
相关资源
最近更新 更多