2014-10-24 7 views
6

Gibt es eine Möglichkeit, die Verbindungen zu Lambda-Funktionen zu trennen, ohne Verbindungsobjekte zu speichern? Qt?So trennen Sie eine Lambda-Funktion, ohne die Verbindung zu speichern

Ich weiß, es ist möglich zu tun, wenn ich die QMetaObject::Connection von der Connect-Funktion zurückgegeben, aber ich möchte nicht wirklich tun, weil es eine Tonne von ihnen wäre. Ich verbinde mich hauptsächlich mit Lambda-Funktionen, um zu vermeiden, dass eine Reihe von einmaligen Methoden und Objekten entsteht, und es scheint, als ob ich die ganze Buchhaltung machen müsste, dass SLOTs vorzuziehen wäre.

+0

Wie würden Sie sich vorstellen, dass Sie dies tun würden, ohne wieder einen Zustand zu speichern? – Yakk

+0

Ich kann mir nicht vorstellen, wie, deshalb habe ich die Gemeinde gefragt. Es scheint nur so, als ob die Lambda-Syntax für Fälle, in denen eine Trennung erforderlich ist, nicht sehr nützlich ist. –

Antwort

4

Hier sind zwei Ansätze, um die Buchhaltungsprobleme zu verbergen.

Erstens halten wir eine std::vector, die auf Zerstörung, uns von der Quelle trennt:

typedef std::shared_ptr<void> listen_token; 

struct disconnecter { 
    QMetaObject::Connection conn; 
    disconnecter( QMetaObject::Connection&& c):conn(std::move(c)) {} 
    ~disconnecter() { QObject::disconnect(conn); } 
}; 

template<class F, class T, class M> 
listen_token QtConnect(T* source, M* method, F&& f) { 
    return std::make_shared<disconnecter>(
    QObject::connect(source, method, std::forward<F>(f)); 
); 
} 

typedef std::vector<listen_token> connections; 

Dann schließen wir wie folgt vor:

connections conns; 
conns.emplace_back(QtConnect(bob, &Bob::mySignal, [](QString str){ std::cout << "Hello World!\n"; })); 

, wenn der Vektor zerstört wird, die Verbindungsobjekte sind auch zerstört.

Dies ist vergleichbar mit anderen Signal/Slot-Systemen, bei denen der Listener ein Token verfolgt und dann zurückgibt. Aber hier halte ich das Trennungsobjekt in einem undurchsichtigen Typ, der die Verbindung bei der Zerstörung aufräumt.

Beachten Sie, dass das Kopieren dieses Vektors die Lebensdauer der Verbindung verlängert. Wenn die Nachricht an eine bestimmte Instanz einer Klasse weitergeleitet wird, speichern Sie eine connections Instanz in der Klasse, und Sie erhalten keine Nachrichten, nachdem die Instanz zerstört wurde.


Ein zweiter Ansatz, der vor, was gefunden @lpapp, ist, wenn Sie einen Lambda haben, die Sie nur einmal in Reaktion auf ein Signal anrufen wollen, dann trennen:

template<class F> 
struct auto_disconnect_t { 
    F f; 
    std::shared_ptr<QMetaObject::Connection> conn; 

    template<class U> 
    auto_disconnect_t(U&& u): 
    f(std::forward<U>(u)), 
    conn(std::make_shared<QMetaObject::Connection>()) 
    {} 

    template<class... Args> 
    void operator()(Args&&... args)const{ 
    QObject::disconnect(*conn); 
    f(std::forward<Args>(args)...); 
    } 
}; 

template<class T, class M, class F> 
void one_shot_connect(T* t, M* m, F&& f) { 
    typedef typename std::decay<F>::type X; 
    auto_disconnect_t<X> helper(std::forward<F>(f)); 
    *helper.conn = QObject::connect(t, m, helper); 
}; 

wir hier one_shot_connect(bob, Bob::mySignal, [](QString str) { std::cout << "Hello\n" }); und das nächste Mal, wenn das Signal ausgelöst wird, erhalten wir die Nachricht, und dann wird die Verbindung getrennt.

Ich trennen Sie vor der Verarbeitung Ihres Lambda, nur für den Fall, dass das Lambda das Signal zu feuern oder etwas verursacht.

+1

Warum verwenden Sie hier shared_ptr statt unique_ptr? – povman

+1

@povman 'std :: function' und die meisten Typlöschungen des Konzepts * invoke * require * copy * ebenfalls (im zweiten Fall oben), und' unique' würde * copy * blockieren. Im ersten Fall würde "unique" funktionieren, aber mehr Eingabe (um einen expliziten Zerstörer zu speichern) oder weniger Abstraktion (um ein explizites Ziel im Token zu speichern, statt "void"). Und andere Listener-Observer-Systeme benötigen andere Daten im Listener-Token. Daher verwende ich 'shared_ptr ' dafür. – Yakk

+0

Tolle Idee, aber Fehler mit Vorlage Argumentabzug und ich habe keine Ahnung warum. Wird der Code im ersten Ansatz kompiliert (mit Bob oder einem anderen Beispiel)? Ich erhalte einen Fehler C2784: 'listen_token QtConnect (T *, M *, F &&)': konnte Template-Argument für 'M *' von 'void (__cdecl QIODevice :: *) (qint64)' nicht ableiten, wenn ich versuche zu verwenden dies mit einem QSerialPort. Ich rufe wie folgt QtConnect (mPort.get(), & QSerialPort :: bytesWritten, [&] (qint64 bytes) {...}; Irgendwelche Ideen, wie ich das beheben kann? Das Legacy-Connect funktioniert gut connect (mPort.get (), & QSerialPort :: bytesWritten, this, & MainWindow :: processTx); – johnco3

0

Dies ist der einzige Weg, wie dies zu schreiben:

QMetaObject::Connection connection = 
    QObject::connect(psender, &MyClass::mySignal, []() { /* Do the work */ }); 
QObject::disconnect(connection); 

die documentation Referenz über die Trenn Methode.

bool QObject :: Disconnect (const QMetaObject :: Verbindungs ​​& Verbindung) [statisch]

Disconnect eine Verbindung.

Wenn die Verbindung ungültig ist oder bereits getrennt wurde, nichts tun und false zurückgeben.

Am Ende des Tages, werden Sie immer noch die Buchhaltung mit Schlitzen auch nur, dass Lambda mehr lokalisiert zu sein scheint, wenn Sie es wirklich brauchen lokalisiert, so ist es bis zu Ihrer persönlichen Präferenz, ich annehmen.

8

Unter der Annahme einer Verbindung:

QObject::connect(senderInstance, &Sender::mySignal, this, []() { 
    // implement slot as a lambda 
}); 

Dann können Sie leicht trennen von:

QObject::disconnect(senderInstance, &Sender::mySignal, this, nullptr); 

Diese alle this's Slots für Sender::mySignal trennen würde; Es ist jedoch durchaus üblich, nur einen solchen Schlitz zu haben, so dass das Endergebnis einfach und ohne Nebenwirkung ist.

+0

Danke David! Ich stimme zu, dass dies wahrscheinlich 60-70% der Szenarien abdeckt. –

+0

Es funktioniert super danke –

3

Sie können ein Dummy-Objekt verwenden:

QObject *obj = new QObject(this); 
QObject::connect(m_sock, &QLocalSocket::readyRead, obj, [this](){ 
    obj->deleteLater(); 

Wenn die obj zerstört wird, wird die Verbindung getrennt, weil Sie die obj auf dem connect übergeben.

+0

Ich mag diese Lösung, da sie einfacher und sauberer ist. sollte erfasst werden, um den Anruf zu tätigen. –

Verwandte Themen