2017-10-30 14 views
23

Rozważmy biblioteki C, która definiuje funkcje do tworzenia, niszczenia i pracy ze strukturą niestandardowegoC++ używać inteligentne kursory ze zmianą wskaźnika wartości

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

Obecnie użyłem biblioteki w moim C++ projektu następująco

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

Rozumiem, dlaczego inteligentne wskaźniki są ważne i chcę przenieść mój kod, aby z nich korzystać. Tak właśnie wygląda kod.

Wydaje się działać, ale wywołanie myfoo2.get() wydaje się być hacky. Czy używam go zgodnie z przeznaczeniem?

Istnieje inna część biblioteki, która tworzy i działa z jakąś strukturą listy. Api wygląda

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

i służy jako

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

Jako wskaźnik (adres wskazany) zmienia się z każdym bar_append inwokacji, jaki chciałbym przedstawić tutaj inteligentne kursory, tak że bar_free_recursive jest wywoływany na bieżącej wartości wskaźnika po zwolnieniu instancji wskaźnika?

+3

Pamiętaj, że możesz przeciążenie * * funkcje. Dlaczego nie utworzyć prostej, wbudowanej przeciążonej funkcji 'foo_action', która pobiera argument' FooPtr' i po prostu wywołuje starą funkcję 'foo_action' z surowym, nie-inteligentnym wskaźnikiem? Nadal można zachować stary kod, a interfejs jest ładniejszy. –

+0

@Someprogrammerdude dobry punkt, dzięki. Po prostu nie byłem pewien, czy jest to w ogóle wymagane, czy też jest bardziej przejrzysty sposób na konwersję. – muffel

+0

@muffel Jeśli używasz nowoczesnego C++, powinieneś używać 'using' zamiast' typedef'. – tambre

Odpowiedz

21

, ale wywołanie myfoo2.get() wydaje się być hacky. Czy używam go zgodnie z przeznaczeniem?

To nie jest hacky, używasz go zgodnie z przeznaczeniem.

chciałbym pójść o krok dalej i zawinąć całość w klasie:

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

W ten sam sposób:

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

Dodatkowa premia: musisz tylko "#include ' w 'FooWrapper.cpp' i' #include 'w' BarWrapper.cpp', jeśli zastosujesz normalne "funkcje składowe zadeklarowane tylko w nagłówku" style – Caleth

+1

Jeśli już owinąć go klasą, dlaczego unikalny wskaźnik? wystarczy utworzyć destruktor, który wywołuje 'foo_free' –

+8

@DavidHaim: Reguła 5/3/0. Z twoją sugestią, musiałbym napisać destruktor, przenieść konstruktora/zadanie. – Jarod42

4

powiedziałbym myfoo2.get() jest przylegający nie hacky .

Osobiście utworzę szablon owijarkę obj_ptr (wybierz bardziej odpowiednią nazwę) i użyj cech dla każdego typu obiektu, aby wymodelować swoje wymagania w sposób C++. Opakowanie może następnie usunąć kruchość dostępu do leżącego pod nim obiektu.

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

konieczności pisania .get() jest niefortunną konsekwencją stosowania inteligentnych wskaźników, ale myślę, że najlepsze praktyki, jeśli chcesz przejść do funkcji, która akceptuje zakaz posiadania, pustych wskaźnika.

Ale w praktyce często stwierdzam, że nie potrzebujesz, aby był on zerowy i może przyjąć referencję zamiast surowej wskazówki.Następnie składnia jest nieco mniej "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 powinien pracować z unique_ptr zapewniając użyć std::move:

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); 
} 
Powiązane problemy