2012-04-25 9 views
26

Ich begann vor kurzem eine Menge meiner vorhandenen C++ Anwendungscode Portierung auf C++ 11 über und jetzt, da ich std den neuen Smart-Pointer bin Umwandlung :: unique_ptr und std: : shared_ptr, ich habe eine spezielle Frage zu benutzerdefinierten Löschern. Ich möchte einen Lambda-Logger hinzufügen, um zu sehen, wo meine Löschungen aufgerufen werden, aber ich kann die Array-Spezialisierungsversion nicht kompilieren. Beratung wäre sehr geschätzt.unique_ptr <T> Lambda benutzerdefinierte deleter für Array Spezialisierung

Ich habe vergeblich für ein Beispiel eines benutzerdefinierten deleter für Array-Spezialisierung unique_ptr für VC++ 10 oder GCC 4.5.2+ suchen. Ich möchte eine Protokollnachricht drucken, wenn die Deletes in einem Lambda aufgerufen werden - hauptsächlich um sicherzustellen, dass alle Zeiger, die ich glaube, außerhalb des Gültigkeitsbereichs liegen, dies tun. Ist dies für die Array-Version der Spezialisierung möglich? Ich kann es mit der Nicht-Array-Version arbeiten, und ich kann es auch mit einer Array-Spezialisierung arbeiten, wenn ich eine externe Struktur "MyArrayDeleter" als zweites Argument übergeben. Noch eine Sache, wäre es möglich, die hässliche std :: function zu entfernen, da ich dachte, dass ich die Lambdasignatur das herausfinden lassen könnte.

struct MySimpleDeleter { 
    void operator()(int* ptr) const { 
     printf("Deleting int pointer!\n"); 
     delete ptr; 
    } 
}; 
struct MyArrayDeleter { 
    void operator()(int* ptr) const { 
     printf("Deleting Array[]!\n"); 
     delete [] ptr; 
    } 
}; 
{ 
    // example 1 - calls MySimpleDeleter where delete simple pointer is called 
    std::unique_ptr<int, MySimpleDeleter> ptr1(new int(5)); 

    // example 2 - correctly calls MyArrayDeleter where delete[] is called 
    std::unique_ptr<int[], MyArrayDeleter> ptr2(new int[5]); 

    // example 3 - this works (but default_delete<int[]> would have been passed 
    // even if I did not specialize it as it is the default second arg 
    // I only show it here to highlight the problem I am trying to solve 
    std::unique_ptr<int[], std::default_delete<int[]>> ptr2(new int[100]); 

    // example 3 - this lambda is called correctly - I want to do this for arrays 
    std::unique_ptr<int, std::function<void (int *)>> ptr3(
     new int(3), [&](int *ptr){ 
      delete ptr; std::cout << "delete int* called" << std::endl; 
     }); 

    // example 4 - I cannot get the following like to compile 
    // PLEASE HELP HERE - I cannot get this to compile 
    std::unique_ptr<int[], std::function<void (int *)>> ptr4(
     new int[4], [&](int *ptr){ 
      delete []ptr; std::cout << "delete [] called" << std::endl; 
     }); 
} 

The compiler error is as follows: 

The error from the compiler (which complains about the new int[4] for ptr4 below is: 
'std::unique_ptr<_Ty,_Dx>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty,_Dx>' 
1>   with 
1>   [ 
1>    _Ty=int [], 
1>    _Dx=std::tr1::function<void (int *)> 
1>   ] 
1>   c:\program files (x86)\microsoft visual studio 10.0\vc\include\memory(2513) : see declaration of 'std::unique_ptr<_Ty,_Dx>::unique_ptr' 
1>   with 
1>   [ 
1>    _Ty=int [], 
1>    _Dx=std::tr1::function<void (int *)> 
1>   ] 
+0

Beispiel 3 speicherte mich .. danke –

Antwort

32

Was:

auto deleter=[&](int* ptr){...}; 
std::unique_ptr<int[], decltype(deleter)> ptr4(new int[4], deleter); 
+2

Der Argumenttyp deines Löschers ist falsch - - das sollte 'int * ptr' sein und nicht' int (* ptr) [] '. – ildjarn

+0

@ildjarn: warum ist' int (* ptr) [] 'falsch? Sollte ich nicht erwarten' std :: unique_ptr ' 'deleter' einen Zeiger auf ein Array von' int' anstelle eines Zeigers auf ein einzelnes int übergeben? Oder ist das buchstabiert 'int * (ptr [])'? – Managu

+4

In C++ ein dynamisches Array von 'int's wird als ein 'int *' dargestellt, dh der Typ von 'new int [4]' ist 'int *'. Folglich erwartet der Deleter ein 'int *' - warum wäre ein zusätzliches Level an Indirektion sinnvoll/notwendig? "Dies wird leicht demonstriert, indem man versucht, _compile_ Ihren Code zu schreiben ..: [funktioniert nicht] (http://ideone.com/fujAk), [funktioniert nicht] (http://ideone.com/JeXYD) kein Kontext ist 'int (* ptr) []' gültige Syntax; Zumindest müsste "int (* ptr) [N]" für einige bekannte "N" als Zeiger auf ein Array mit statischer Größe fungieren. – ildjarn

4

Zunächst ersten, verwende ich VC2010 mit SP1, Mingw g ++ 4.7.1

Für Array neu, unique_ptr es bereits in einer sauberen Art und Weise unterstützen:

struct X 
{ 
    X() { puts("ctor"); } 
    ~X() { puts("dtor"); } 
}; 

unique_ptr<X[]> xp(new X[3]); 

Die Ausgabe lautet:

ctor 
ctor 
ctor 
dtor 
dtor 
dtor 

Für kundenspezifische deleter leider ist es inkonsequent zwischen VC2010 und g ++:

VC2010:

unique_ptr<FILE, function<void (FILE*)> > fp(fopen("tmp.txt", "w"), [](FILE *fp){ 
    puts("close file now"); 
    fclose(fp); 
    }); 

g ++:

unique_ptr<FILE, void (*)(FILE*) > fp(fopen("tmp.txt", "w"), [](FILE *fp){ 
    puts("close file now"); 
    fclose(fp); 
    }); 

Das Verfahren von Managu ist sehr gut, weil Inline-Lambda ist cool, aber verletzte Lesbarkeit IMHO. Es stellt auch fest, dass die Ressource vor dem Erwerb freigegeben wird (RAII).

Hier schlage ich vor, eine declartive Weise Ressourcenerfassung zu trennen und freigeben (Scope Guard funktioniert sowohl für VC2010 und g ++ 4.7.1):

template<typename T> 
struct ScopeGuard 
{ 
    T deleter_; 
    ScopeGuard(T deleter) : deleter_(deleter) {} 
    ~ScopeGuard() { deleter_() ; } 
}; 
#define UNI_NAME(name, line) name ## line 
#define ON_OUT_OF_SCOPE_2(lambda_body, line) auto UNI_NAME(deleter_lambda_, line) = [&]() { lambda_body; } ; \ 
     ScopeGuard<decltype(UNI_NAME(deleter_lambda_, line))> \ 
     UNI_NAME(scope_guard_, line) (UNI_NAME(deleter_lambda_, line)); 
#define ON_OUT_OF_SCOPE(lambda_body) ON_OUT_OF_SCOPE_2(lambda_body, __LINE__) 

FILE * fp = fopen("tmp.txt", "w"); 
ON_OUT_OF_SCOPE({ puts("close file now"); fclose(fp); }); 

Der Punkt ist, dass Sie eine Ressource in den alten zu bekommen, Deaktivieren Sie die Anweisung, um die Ressource sofort nach der Ressourcenerfassungszeile freizugeben.

Der Nachteil ist, dass Sie nicht ein einzelnes Objekt um zusammen mit seinem deleter weiterleiten können.

Für FILE *, shared_ptr kann für den gleichen Zweck als Alternative Pointer verwendet werden (vielleicht ein wenig schwergewichtigen, aber gut funktioniert sowohl für VC2010 und g ++)

Shared_ptr fp2 (fopen ("tmp.txt "," w "), [] (FILE * fp) {fclose (fp); puts (" Datei schließen ");});