with - unique_ptr c++ custom deleter




unique_ptr<T> lambda custom deleter für Array-Spezialisierung (2)

Wie wäre es mit:

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

Ich habe vor kurzem begonnen, viele meiner bestehenden C ++ - Anwendungscode auf über C ++ 11 portieren und jetzt, dass ich zu den neuen Smart-Zeigern std :: unique_ptr und std :: shared_ptr konvertieren, habe ich eine spezielle Frage über benutzerdefinierte deleters. 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.

Ich habe vergeblich nach einem Beispiel eines benutzerdefinierten Löschers für die Array-Spezialisierung unique_ptr für VC ++ 10 oder GCC 4.5.2+ gesucht . 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 :: -Funktion zu entfernen, da ich dachte, dass ich die Lambda-Signatur 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>          ]

Zuerst verwende ich VC2010 mit SP1, Mingw g ++ 4.7.1

Für das Array new unterstützt unique_ptr es bereits sauber:

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

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

Die Ausgabe ist:

ctor
ctor
ctor
dtor
dtor
dtor

Bei benutzerdefinierten Deletern ist es zwischen VC2010 und g ++ leider inkonsistent:

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);
  });

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

Hier schlage ich eine deklarative Möglichkeit vor, Ressourcenerwerb und -freigabe zu trennen (Scope Guard, funktioniert sowohl für VC2010 als auch für 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 der alten, klaren Weise erhalten und die Anweisung so deklarieren können, dass sie die Ressource sofort nach der Ressourcenerfassungslinie freigibt.

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

Für FILE * kann shared_ptr als alternativer Zeiger für den gleichen Zweck verwendet werden (vielleicht ein wenig schwer, aber funktioniert gut für VC2010 und g ++)

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





unique-ptr