2017-10-30 5 views
23

eine C-Bibliothek Betrachten Sie die Funktionen zum Erstellen, zu zerstören und die Arbeit mit einer benutzerdefinierten StrukturC++ Smart-Pointers verwenden mit wechselnden Zeigerwerte

struct Foo; 
void foo_action(Foo*); 
Foo* foo_create(); 
void foo_free(Foo*); 

Zeit definiert, benutzte ich die Bibliothek in meinem C++ Projekt als

folgt
Foo* myfoo = foo_create(); 
foo_action(myfoo); 
foo_free(myfoo); 

Ich verstehe, warum Smart Pointer wichtig sind und möchten meinen Code migrieren, um sie zu verwenden. So sieht der Code jetzt aus.

#include <memory> 
#include <functional> 
typedef std::unique_ptr<Foo, std::function<void(Foo*)>> FooPtr; 
// ... 
FooPtr myfoo2(foo_create(), foo_free); 
foo_action(myfoo2.get()); 

Es scheint zu funktionieren, aber die myfoo2.get() Aufruf scheint hacky. Verwende ich es wie vorgesehen?

Es gibt einen anderen Teil der Bibliothek, der mit einer Art Listenstruktur erstellt und arbeitet. Die api sieht aus wie

struct Bar; 
Bar* bar_append(Bar*, int); 
void bar_free_recursive(Bar*); 

und wird als

// using NULL as current Bar* creates the initial structure 
Bar* bar = bar_append(NULL, 1); 
// each invocation leads to another 'head' structure 
bar = bar_append(bar, 42); 
bar = bar_append(bar, 123); 

Als Zeiger (die Adresse zeigte auf) ändert sich mit jeder bar_append Aufruf verwendet, wie würde ich intelligente Zeiger hier einführen, so dass bar_free_recursive aufgerufen wird auf dem aktuellen Zeigerwert, wenn die Zeigerinstanz freigegeben wird?

+3

Denken Sie daran, dass Sie * Überlastung * Funktionen. Warum nicht eine einfache Inline-überladene 'foo_action'-Funktion erstellen, die ein' FooPtr'-Argument verwendet und einfach die alte 'foo_action'-Funktion mit dem rohen nicht-intelligenten Zeiger aufruft? Sie können weiterhin den alten Code behalten, während Sie die Schnittstelle netter machen. –

+0

@Someprogrammerdude guten Punkt, danke. Ich war mir nur nicht sicher, ob es überhaupt erforderlich ist, oder ob es einen direkteren Weg für die Konvertierung gibt. – muffel

+0

@muffel Wenn Sie modernes C++ verwenden, sollten Sie 'using' anstelle von' typedef' verwenden. – tambre

Antwort

21

aber der myfoo2.get() Aufruf scheint hacky. Verwende ich es wie vorgesehen?

Es ist nicht hacky, Sie verwenden es wie beabsichtigt.

würde ich einen Schritt weiter gehen und das Ganze in einer Klasse wickeln:

struct Foo; 
void foo_action(Foo*); 
Foo* foo_create(); 
void foo_free(Foo*); 

class FooWrapper 
{ 
public: 
    FooWrapper() : mFoo(foo_create()) {} 

    void action() { foo_action(mFoo.get()); } 
private: 
    struct FooDeleter 
    { 
     void operator()(Foo* foo) const { foo_free(foo); } 
    }; 

    std::unique_ptr<Foo, FooDeleter> mFoo; 
}; 

Auf die gleiche Weise:

struct Bar; 
Bar* bar_append(Bar*, int); 
void bar_free_recursive(Bar*); 

class BarWrapper 
{ 
public: 
    explicit BarWrapper(int n) : mBar(bar_append(nullptr, n)) {} 

    void append(int n) { mBar.reset(bar_append(mBar.release(), n)); } 

private: 
    struct BarDeleter 
    { 
     void operator()(Bar* bar) const { bar_free_recursive(bar); } 
    }; 

    std::unique_ptr<Bar, BarDeleter> mBar; 
}; 
+0

Zusätzlicher Bonus: Sie müssen nur '#include ' in' FooWrapper.cpp' und '#include ' in 'BarWrapper.cpp' einschließen, wenn Sie den normalen" Elementfunktionen, die nur im Header deklariert sind "style – Caleth

+1

Wenn Sie bereits wickle es mit einer Klasse, warum eindeutiger Zeiger? mach einfach einen Destruktor, der 'foo_free' ruft –

+8

@DavidHaim: Regel von 5/3/0. Mit Ihrem Vorschlag müsste ich Destruktor schreiben, Konstruktor/Zuweisung verschieben. – Jarod42

4

Ich würde sagen, myfoo2.get() ist klobig, nicht Hacky .

Ich würde persönlich erstellen eine Vorlage basierte Wrapper obj_ptr (Sie wählen einen relevanteren Namen) und verwenden Merkmale für jeden Objekttyp, um Ihre Anforderung der C++ Art zu modellieren. Der Wrapper kann dann die Clunky des Zugriffs auf das zugrunde liegende Objekt entfernen.

template <typename T, typename Traits> 
class obj_ptr final 
{ 
    std::unique_ptr<Foo, void(*)(T*)> ptr_{ Traits::create(), Traits::free }; 

public: 
    operator T*() { return ptr_.get(); } 

    operator const T*() const { return ptr_.get(); } 

    T* operator->() { return ptr_.get(); } 

    const T* operator->() const { return ptr_.get(); } 
}; 

class foo_traits 
{ 
public: 
    static Foo* create() { return foo_create(); } 

    static void free(Foo* foo) { foo_free(foo); } 
}; 

int main() 
{ 
    using FooPtr2 = obj_ptr<Foo, foo_traits>; 

    FooPtr2 myfoo2; 

    foo_action(myfoo2); 

    return EXIT_SUCCESS; 
} 
5

Mit .get() ist eine unglückliche Folge der Verwendung von Smart-Pointer zu schreiben, aber ich denke, am beste Praxis, wenn Sie eine Funktion zu übergeben wollen, das ein nicht-besitzenden, Nullable-Zeiger akzeptiert.

Aber in der Praxis finde ich oft, dass Sie keine NULL-Werte benötigen und eine Referenz anstelle eines Raw-Pointers akzeptieren können.Dann ist die Syntax ein bisschen weniger "Hacky":

void foo_action(Foo&); // accept a reference instead of a raw-pointer 

struct FooDeleter { 
    void operator()(Foo* foo) const { foo_free(foo); } 
}; 

using FooPtr = std::unique_ptr<Foo, FooDeleter>; 

FooPtr make_foo() { 
    return FooPtr(foo_create()); 
} 

int main() { 
    auto foo = make_foo(); 

    // ... 

    if (foo) {    // check for null 
     foo_action(*foo); // dereference smart-pointer 
    } 
} 

bar_append sollte mit einem unique_ptr arbeiten Sie std::move verwenden Bereitstellung:

struct BarDeleter { 
    void operator()(Bar* bar) const { bar_free_recursive(bar); } 
}; 

using BarPtr = std::unique_ptr<Bar, BarDeleter>; 

BarPtr bar_append(BarPtr bar, int value) { 
    return BarPtr(bar_append(bar.release(), value)); 
} 

int main() {  
    BarPtr bar; 
    bar = bar_append(std::move(bar), 42); 
    bar = bar_append(std::move(bar), 123); 
} 
Verwandte Themen