2009-08-13 17 views
4

Ich habe eine Situation, in der eine boost :: function und boost :: bind (eigentlich eine std :: tr1 :: function und bind) gelöscht werden, solange sie noch benutzt werden. Ist das sicher? Ich würde es normalerweise vermeiden, aber der störende Code ist ein wenig eingegraben und meine einzige andere Möglichkeit ist das Hinzufügen eines neuen Threads.Boost-Funktion während der Verwendung löschen

typedef function<int(int)> foo_type; 

foo_type* global_foo = NULL; 

int actual_foo(int i, Magic* m) 
{ 
    delete global_foo; 
    return m->magic(i); 
} 

int main() 
{ 
    Magic m; 
    global_foo = new foo_type(bind(&actual_foo, _1, &m); 

    return (*global_foo)(10) 
} 

Die gebundenen Parameter sind immer Ebene integral Typen (int und einen Zeiger in dem realen Code), und nicht die Referenzen.

+0

Zusammenfassung, kann es gefährlich sein abhängig davon, was der Operator() Aufruf in der Implementierung tut . Sie haben keine Garantie, was es tut, also nehmen Sie an, es ist gefährlich. Die aktuelle Implementierung von boost :: function wäre sicher. –

Antwort

5

boost::function oder std::tr1::functions sind kopierbare Objekte. Also, im Allgemeinen gibt es absolut keinen Grund, sie zuzuteilen - übergeben Sie sie einfach nach Wert.

Sie sind gut für die meisten realen Fällen optimiert ... So sie nur nach Wert übergeben:

typedef function<int(int)> foo_type; 

foo_type global_foo; 

int actual_foo(int i, Magic* m) 
{ 
    delete global_foo; 
    return m->magic(i); 
} 

int main() 
{ 
    Magic m; 
    global_foo = bind(&actual_foo, _1, &m); 

    return global_foo(10) 
} 

Der Code, den Sie ist gefährlich vorgeschlagen, diesen Code ausführen:

#include <boost/function.hpp> 
    #include <boost/bind.hpp> 
    #include <iostream> 
    using namespace std; 

    boost::function<void()> *glb; 

    struct Data { 
      int x; 
      Data(int _x = 0) : x(_x) { cout<<"ctor:"<<this<<endl; } 
      ~Data() { cout<<"dtor:"<<this<<endl; } 
      Data(Data const &p) {x=p.x; cout<<"ctor:"<<this<<endl; } 
      Data const &operator=(Data const &p) { x=p.x; cout<<this<<"="<<&p<<endl; return *this; } 
    }; 

    void func(Data const &x) 
    { 
      delete glb; 
      cout<<&x<<endl; 
    } 

    int main() 
    { 
      glb=new boost::function<void()>(boost::bind(func,Data(3))); 

      (*glb)(); 
      return 0; 
    } 

Sie würden finden Sie, dass Sie versuchen, in func auf destoryed Objekt (dtor mit dem gleichen Zeigerwert) zuzugreifen, die in cout<<&x<<endl bereits aufgerufen wird.

Denn wenn Sie Ihre Funktion zerstören Objekte, die Sie auch die binded Parameter zerstören Sie haben und Data const &x unavailible werden becuase es mit global_function

bearbeiten destoryed wurde: clearification für einen Kommentar:

Wenn Sie etwas haben, wie

map<string,function<void()> > calls; 

void delete_key(){ 
    calls.erase("key"); 
} 

main() 
{ 
    calls["key"]=delete_key; 

    // Wrong and dangerous 
    // You delete function while in use 
    calls["key"](); 

    // Correct and safe 
    // You create a copy of function so it would not 
    // be deleted while in use. 
    function<void()> f=calls["key"]; 
    f(); 
} 
+1

Ziemlich sicher, dass Sie global_foo nicht mehr löschen möchten. – Salgar

+0

Sieht so aus, als ob ich das Problem zu sehr vereinfacht habe. global_foo wird nach Wert in einer Map gespeichert. Es ist möglich, die Funktion <> während des Aufrufs aus der Karte zu entfernen. Ich weiß, dass es gefährlich wäre, wenn die Lebensdauer des Parameters nicht garantiert werden kann (struct Data). Aber ich habe versucht klarzustellen, dass die gebundenen Parametypen ganzzahlig sind –

2

Bearbeiten: Nachdem ich diese Antwort ein wenig mehr (mit Charles Bailey) diskutiert habe, glaube ich, dass dies unsicher ist, da es auf die Implementierung von boost :: function ankommt.

Der Call-Stack, wenn wir actual_foo geben wird wie folgt aussehen:

actual_foo 
boost::function::operator() 
main 

Also, wenn actual_foo Ausführung beendet hat, werden wir wieder in den operator() Code von boost::function springen, und das Objekt gelöscht wurde. Dies ist nicht garantiert ein Problem zu sein - es ist ein bisschen wie delete this aufrufen - aber Sie müssen sehr vorsichtig sein, was Sie in einer Member-Funktion eines gelöschten Objekts tun. Sie dürfen keine virtuellen Funktionen aufrufen oder Datenelemente verwenden.

Das Problem ist, dass ich nicht bewusst bin, dass boost::function macht keine Garantien über das, was es in operator() nach der Funktion es Wraps hat aufgerufen wurde. Es scheint auf meiner Plattform, dass es nichts Gefährliches tut (also beschweren sich Valgrind nicht), aber es ist durchaus vorstellbar, dass es auf einer anderen Plattform, mit einer anderen Implementierung oder mit verschiedenen Compiler-Flags etwas tun möchte, was nicht stimmt 't erlaubt, sobald das Objekt gelöscht wurde - zum Beispiel könnte es einige Debug-Informationen mit einer Member-Variable schreiben.

Also ich glaube, das ist eine gefährliche Sache zu tun, die unter bestimmten Umständen zu undefiniertem Verhalten führen kann.

Weitere Anmerkung:

nahm ich einen kurzen Blick auf, was Auftrieb wirklich tut, nachdem er den Funktionszeiger aufruft. Sieht man sich hier an: http://boost.cvs.sourceforge.net/viewvc/boost/boost/boost/function/function_template.hpp?view=markup an der operator() -Funktion in Zeile 687, meine Interpretation ist, dass es sofort den Rückgabewert zurückgibt und nichts anderes tut, also in der Praxis sollte man mit dieser Implementierung in Ordnung sein, aber die Kommentare dazu Sei gefährlich noch halt. Beachten Sie, dass ich sehr gut das falsche Stück Code gefunden haben, und/oder verstanden es falsch ...

Ursprüngliche Antwort unten:

Ich glaube, das ist ok. Wenn Sie actual_foo eingeben, haben die Objekte boost::bind und boost::function ihre Jobs beendet, und Sie führen die echte Funktion actual_foo aus.

Ich überprüfte dies auf meiner Plattform (gcc 4.2.4, Linux) durch Ausführen des Programms durch valgrind.Hier

ist das Programm, das ich lief:

#include <boost/bind.hpp> 
#include <boost/function.hpp> 

class Magic 
{ 
public: 
    int magic(int i) 
    { 
     return 5; 
    } 
}; 

typedef boost::function<int(int)> foo_type; 

foo_type* global_foo = NULL; 

int actual_foo(int i, Magic* m) 
{ 
    delete global_foo; 
    return m->magic(i); 
} 

int main() 
{ 
    Magic m; 
    global_foo = new foo_type(boost::bind(&actual_foo, _1, &m)); 

    return (*global_foo)(10); 
} 

und hier ist die valgrind Ausgang:

$ valgrind ./boost_bind 
==17606== Memcheck, a memory error detector. 
==17606== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al. 
==17606== Using LibVEX rev 1804, a library for dynamic binary translation. 
==17606== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP. 
==17606== Using valgrind-3.3.0-Debian, a dynamic binary instrumentation framework. 
==17606== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al. 
==17606== For more details, rerun with: -v 
==17606== 
==17606== 
==17606== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 17 from 1) 
==17606== malloc/free: in use at exit: 0 bytes in 0 blocks. 
==17606== malloc/free: 1 allocs, 1 frees, 16 bytes allocated. 
==17606== For counts of detected errors, rerun with: -v 
==17606== All heap blocks were freed -- no leaks are possible. 

muss ich sagen aber, dass dies eine seltsame Sache scheint zu tun. Es wäre mir viel lieber, wenn es möglich wäre, das Funktionsobjekt automatisch zu löschen, indem man es zu einer Stapelvariablen macht, oder es im Destruktor einer Stapelvariablen zu löschen (wie in RAII). Es wäre sauberer, sicherer, weniger gruselig und ausnahmesicher. Aber ich bin mir sicher, dass Sie das alles bereits wissen, und es gibt gute Gründe, mit dem Code zu leben, wie er ist.

+0

-1 Dies ist falsch - der Code ist gefährlich – Artyom

+0

Bitte erläutern Sie, warum es gefährlich ist. Meinst du, es ist nicht ratsam (stimme zu), oder meinst du, es verursacht undefiniertes Verhalten (ich bin noch nicht überzeugt)? –

+0

Ich bemerke Ihren (Artyoms) Punkt in Ihrer Antwort, dass, wenn das Bind-Objekt gelöscht wurde, die Argumente zu actual_foo möglicherweise gelöscht wurden. Da die Argumente hier jedoch wertmäßig übergeben werden, glaube ich nicht, dass dies der Fall ist. Ich bin eine lokale Kopie eines int, und m ist eine lokale Kopie eines Zeigers auf die Stapelvariable, die in main() initialisiert wird. –

Verwandte Themen