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....
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