2016-04-13 5 views
1

Ich benutze Swig, um eine Klasse zu implementieren, die in C++ implementiert ist. Diese Klasse verwendet eine flüssige Schnittstelle, um die Verkettung von Methoden zu ermöglichen. Das heißt, Methoden, die den Status eines Objekts ändern, geben den Verweis auf das Objekt zurück und ermöglichen so, die nächste Zustandsänderungsmethode aufzurufen. Zum Beispiel:Swig verwenden, um fließende Interfaces zu weben

class FluentClass { 
public: 
    ... 
    FluentClass & add(std::string s) 
    { 
     state += s; 
     return *this; 
    } 
    ... 
private: 
    std::string state; 
}; 

Verfahren add die angegebene Zeichenfolge s-state und gibt die Referenz auf das Objekt mehrere Anrufe von add fügt so dass man der Kette:

FluentClass fc; 
c.add(std::string("hello ")).add(std::string("world!")); 

Sie finden umfassendere Beispiele an : https://en.wikipedia.org/wiki/Fluent_interface

Ich schrieb mehrere Swing-Dateien (nichts besonderes), um Bindungen für mehrere Sprachen zu erstellen, insbesondere: C#, Java, Python und Ruby . Das folgende Beispiel (Python) wie erwartet funktioniert:

fc = FluentClass() 
fc.add("hello").add("world!") 

jedoch folgendes nicht:

fc = FluentClass() 
fc = fc.add("hello").add("world!") 

Ich fand, dass add auf fc Aufruf die Fundstelle der fc zurückkehrt, sondern eine Referenz (I erwarte, dass die anderen Bindungen dasselbe tun) für ein neu erstelltes Objekt, das den gleichen Speicher tatsächlich umschließt:

fc = FluentClass() 
nfc = fc.add("hello world!") 
fc != nfc, though fc and nfc wrap the same memory :(

Daher führt das Zuweisen des Ergebnisses add zu derselben Variablen zur Zerstörung des ursprünglichen Objekts als Teil der Speicherbereinigung. Das Ergebnis ist, dass fc jetzt auf ungültigen Speicher zeigt.

Also meine Frage ist: Wissen Sie, wie man FluentClass richtig wickeln, um add die gleiche Referenz zurückgeben, um Garbage Collection zu verhindern?

+0

Sie haben ein Speicherbesitzproblem.(Der Aufruf von constructor führt zu einem Proxy, der die Eigenschaft "besitzt", auf die er verweist, die beim Erstellen des neuen, nicht besitzenden Proxys zerstört wird). Ich denke, es gibt einen süßen Trick, den wir verwenden können, um diese Arbeit viel besser zu machen, aber ich werde nicht in der Lage sein, es bis heute Abend zu bestätigen. – Flexo

Antwort

0

Das Problem ist, dass das zugrunde liegende C++ - Objekt gelöscht wird, wenn der beim Erstellen der Instanz erstellte Python-Proxy zerstört wird. Da SWIG nicht weiß, dass der zurückgegebene Wert eine Referenz auf dasselbe Objekt ist, erstellt er einen neuen Proxy, wenn Sie add aufrufen. In dem Fall, in dem Sie Fehler festgestellt haben, hat das ursprüngliche Objekt den ref count hit 0, bevor die verketteten Methoden beendet sind.

Um das Problem zuerst zu untersuchen und zu beheben, habe ich einen Testfall erstellt, um das Problem ordnungsgemäß zu reproduzieren. Das war fließend.h:

#include <string> 

class FluentClass { 
public: 
    FluentClass & add(std::string s) 
    { 
     state += s; 
     return *this; 
    } 
private: 
    std::string state; 
}; 

Genug Code zu treffen zuverlässig SEGFAULT/SIGABRT in Tests in Python:

import test 

def test_fun(): 
    f=test.FluentClass() 
    f=f.add("hello").add("world") 

    return f 

for i in range(1000): 
    f2=test_fun() 
    f2.add("moo") 

Und eine SWIG Interface-Datei mit dem Modul 'Test' zu bauen:

%module test 

%{ 
#include "fluent.h" 
%} 

%include <std_string.i> 

%include "fluent.h" 

Mit Durch diese zusätzliche Arbeit konnte ich das von Ihnen gemeldete Problem reproduzieren. (Anmerkung: In diesem Zusammenhang ziele ich auf SWIG 3.0 mit Python 3.4).

Sie müssen Typmaps schreiben, um den speziellen Fall zu behandeln, in dem 'Wert zurückgegeben == this'. Ich wollte zunächst die argout typeMap des speziellen "this" -Arguments anvisieren, da dies der richtige Ort für diese Art von Arbeit war, aber auch auf den Destruktor-Aufruf, der das Schreiben der Typmaps korrekter als nötig gemacht hätte also habe ich das übersprungen.

In meiner Out Typemap, die nur auf fließende Typen angewendet wird, überprüfe ich, ob wir wirklich die Annahme "Eingabe ist Ausgabe" erfüllen und nicht einfach etwas anderes zurückgeben. Dann gibt es den Referenzzähler der Eingabe, so dass ich ihn mit der erwarteten Semantik zurückgeben kann.

Um dies in der Out-Typ-Map arbeiten zu können, müssen wir noch etwas mehr Arbeit leisten, um das Python-Eingabeobjekt sicher und robust zu erfassen. Das Problem hierbei ist, dass SWIG die folgende Funktionssignatur erzeugt:

SWIGINTERN PyObject *_wrap_FluentClass_add(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { 

wo der SWIGUNUSEDPARAM marco erweitert einfach nicht die ersten Parameter nennt überhaupt. (Das sieht für mich in der Makrodefinition wie ein Fehler aus, da es die Nebenversion von GCC ist, die bestimmt, welche Option im C++ - Modus ausgewählt wird, aber trotzdem wollen wir, dass sie immer noch funktioniert).

Also was ich getan habe, war ein Schreiben einer benutzerdefinierten in Typmap, die zuverlässig erfasst den C++ - dieser Zeiger und das zugehörige Python-Objekt. (Die Art und Weise, wie es geschrieben wird, funktioniert auch, wenn Sie eines der anderen Argumente zum Entpacken von Stilen aktivieren und robust gegenüber anderen Varianten sein sollten. Es wird jedoch fehlschlagen, wenn Sie andere Argumente als "selbst" bezeichnen). Um die Werte irgendwo aus der späteren 'out'-Typmap zu setzen und keine Probleme mit goto-Anweisungen zu haben, müssen wir das _global_ Präfix verwenden, wenn declaring local variables.

Schließlich müssen wir im nicht fließenden Fall etwas vernünftiges tun. So sieht die resultierende Datei wie:

%module test 

%{ 
#include "fluent.h" 
%} 

%include <std_string.i> 
%typemap(in) SWIGTYPE *self (PyObject *_global_self=0, $&1_type _global_in=0) %{ 
    $typemap(in, $1_type) 
    _global_self = $input; 
    _global_in = &$1; 
%} 

%typemap(out) FLUENT& %{ 
    if ($1 == *_global_in) { 
    Py_INCREF(_global_self); 
    $result = _global_self; 
    } 
    else { 
    // Looks like it wasn't really fluent here! 
    $result = SWIG_NewPointerObj($1, $descriptor, $owner); 
    } 
%} 

%apply FLUENT& { FluentClass& }; 

%include "fluent.h" 

Mit %apply hier macht die Steuerung von wo diese einfachen und Generika verwendet wird.


Als beiseite können Sie auch SWIG sagen, dass Ihre FluentClass::add Funktion ihr erstes Argument verbraucht und erstellt eine neue, mit:

%module test 

%{ 
#include "fluent.h" 
%} 

%include <std_string.i> 

%delobject FluentClass::add; 
%newobject FluentClass::add; 

%include "fluent.h" 

, die in eine viel einfachere Art und Weise richtiger Code generiert durch die Entkopplung der Tod des ersten Proxy aus dem echten Löschaufruf. Gleichermaßen, obwohl es ausführlicher ist, dies für jede Methode schreiben zu müssen, und es wäre nicht immer in allen Fällen korrekt, obwohl es in meinem Testfall korrekt ist, z.B.

f1=test.FluentClass() 
f2=f.add("hello").add("world") # f2 is another proxy object, which now owns 
f3=f1.add("again") # badness starts here, two proxies own it now.... 
+0

Ihre Lösung sieht bisher gut aus. Aber glaubst du wirklich, wir müssen die ref count erhöhen (Py_INCREF (_global_self);). Ich meine, wir geben die gleiche Instanz ($ result = _global_self;) zurück und erwarten daher, dass 'f2' gleich ist mit' f' ('f2' ==' f'). Ich habe es aber noch nicht getestet. – Marcel

+0

In Python haben Sie jetzt zwei Möglichkeiten, sich auf dasselbe zu beziehen. Also sollte der Ref-Wert 2 sein. Ich denke, ich kann das klarer demonstrieren, also werde ich am Wochenende ein bisschen mehr darüber schreiben. (Die kurze Version ist, dass die Eingabeargumente nur geliehene Referenzen sind). Zu Vergleichszwecken müssen Sie auch dann, wenn Sie das globale Singleton-Paar Py_True zurückgeben, die Ref-Anzahl noch erhöhen, um korrekt zu sein. – Flexo

-1

Der folgende Code würde für Ruby und Python funktionieren.

%{ 
typedef FluentClass FC_SELF; 
%} 

%typemap(out) FC_SELF& { $result = self; } 

class FluentClass { 
public: 
    FC_SELF& add(const std::string& s); 
}; 

„selbst“ ist der Variablenname des C-Zeiger verwendet in Ruby und Python C API auf eine Selbst Objekt Bezug zu nehmen. Wenn also der Rückgabetyp einer Methode FC_SELF ist, gibt die Methode das self-Objekt zurück. Derselbe Trick würde für andere Sprachen gelten. Aber mit einem intelligenten Zeiger ist definitiv eine bessere Lösung, die in anderen Antworten sein wird.

+0

Auch wenn diese Typmap kompiliert (ich kann es noch nicht testen, aber erwarte es nicht), wird es definitiv nicht in Python zählen. – Flexo

+0

Dies kompiliert nicht mit SWIG 3.0, das auf Python 3 abzielt - die Definition von SWIGUNUSED ist so, dass das ignorierte Funktionsargument tatsächlich keinen Namen hat und nicht darauf zugegriffen werden kann. Du brauchst auch eine 'Py_INCREF', um die Refcounting richtig zu machen, denke ich, und ich würde nicht empfehlen, typedef so zu benutzen. ('% apply' wäre ein besserer Weg, um das gleiche Ergebnis zu erhalten). – Flexo