web-dev-qa-db-ja.com

C ++は、ポインター値を変更するスマートポインターを使用します

カスタム構造を作成、破棄、および操作するための関数を定義するCライブラリを検討してください

_struct Foo;
void foo_action(Foo*);
Foo* foo_create();
void foo_free(Foo*);
_

現在、私は次のように私のC++プロジェクトでライブラリを使用しました

_Foo* myfoo = foo_create();
foo_action(myfoo);
foo_free(myfoo);
_

スマートポインターが重要である理由を理解しており、コードを移行して使用します。これがコードの外観です。

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

機能しているようですが、myfoo2.get()の呼び出しはハッキリしているようです。 意図したとおりに使用していますか?

ライブラリには、ある種のリスト構造を作成して操作する別の部分があります。 APIは次のようになります

_struct Bar;
Bar* bar_append(Bar*, int);
void bar_free_recursive(Bar*);
_

そしてとして使用されます

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

ポインター(指しているアドレス)が_bar_append_の呼び出しごとに変わるので、ここでスマートポインターを導入して、ポインターインスタンスが解放されたときに_bar_free_recursive_が現在のポインター値で呼び出されるようにするにはどうすればよいですか?

23
muffel

しかし、myfoo2.get()の呼び出しはハッキリしているようです。意図したとおりに使用していますか?

ハックではありません。意図したとおりに使用します。

さらに一歩進んで、全体をクラスにラップします。

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

同じやり方で:

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

.get()を記述しなければならないのは、スマートポインターを使用することの不幸な結果ですが、所有していない、null許容のポインターを受け入れる関数に渡す場合は、ベストプラクティスだと思います。

しかし、実際には、null可能にする必要はなく、生のポインタの代わりに参照を受け入れることができることがよくあります。次に、構文は少し「ハッキー」です:

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_appendunique_ptrを使用する場合、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);
}
5
Chris Drew

私はmyfoo2.get()clunkyではなく、-hackyではないと言います。

私は個人的にテンプレートベースのラッパーを作成しますobj_ptr(より適切な名前を選択)し、オブジェクトのタイプごとに特性を使用して、C++の方法で要件をモデル化します。その後、ラッパーは、基になるオブジェクトにアクセスする煩雑さを取り除くことができます。

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