2015-08-31 13 views
8

Sto scrivendo un wrapper Haskell per una classe C++. Ho deciso di rappresentarlo come una struttura dati Haskell contenente un puntatore (Foreign.Ptr) all'istanza della classe in C++. Qualcosa del genere.Chiamare una funzione alla cancellazione dell'oggetto in Haskell

In 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(); 
} 

In 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 

Il problema è, non so come implementare correttamente MyClass eliminazione. Ad un certo punto, Garbage Collector Haskell cancellerà l'oggetto MyClass, ma non attiverà la memoria MyClass * liberando in C++. Come lo aggiusto?

Sono consapevole di ForeignPtr, ma utilizza IO Monade, che non è soddisfacente perché voglio la struttura di dati avvolto a comportarsi esattamente come un normale struttura dati Haskell, senza la necessità di ripartisce esplicito/liberare memoria o IO monadi .

+3

Il tuo effetto collaterale di classe è gratuito? In caso contrario, come è comune in C++, si deve _ad usare la monade 'IO', o _carefully_ assicurarti di esporre un'interfaccia pura a quella. Questo, o perdere la trasparenza referenziale, che è un peccato mortale in Haskell poiché renderà quasi impossibile prevedere il comportamento del programma, specialmente quando l'ottimizzatore riscrive il tuo codice assumendolo. – chi

+0

@chi Sì, è privo di effetti collaterali. – wrwt

risposta

7

“ utilizza IO Monade, che non è soddisfacente perché voglio la struttura di dati avvolto a comportarsi esattamente come un normale struttura di dati Haskell ”

Certo che lo sai, ma purtroppo non è davvero possibile. Le funzioni estere “ ” possono sempre fare cose divertenti che non dovrebbero essere possibili in Haskell; il sistema di tipi non ha modo di guardare lì e prevenirlo.

Questo dilemma è l'unico (!) Motivo per cui abbiamo unsafePerformIO, e in effetti il ​​tuo è un buon esempio di un'applicazione valida per quella cosa.

che non ho ancora fatto questo io stesso, ma il vostro codice dovrebbe essere simile al seguente:

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 

io non sono molto sicuro di quei FunPtr s, si spera che qualcuno commenterà qualcosa su che ...

+0

Ho pensato a una soluzione del genere usando 'unsafePerformIO' su' ForeignPtr', ma non sono sicuro che funzionerà correttamente. Se lo sei, puoi spiegare cosa sta succedendo dietro le quinte in quell'approccio? – wrwt

+1

@wrwt Ogni volta che 'mkMyClass' viene valutato (dovrebbe essere solo una volta, ma non fare affidamento su di esso) viene assegnato un oggetto C++, un valore Haskell viene creato con un finalizzatore collegato a tale e restituito come valore puro. Quando uno di questi valori è garbage collection, verrà eseguito il finalizzatore ('cppDeleteMyClass'). – chi

+0

Informazioni su 'FunPtr's. Il modo corretto per dichiarare 'cppDeleteMyClass' è' import import ccall "& cpp_delete_MyClass" cppDeleteMyClass :: FunPtr (Ptr() -> IO()) '(con & aggiunto). Probabilmente dovresti correggere la tua risposta. – wrwt

Problemi correlati