web-dev-qa-db-ja.com

std :: unique_ptrメンバーでカスタム削除機能を使用するにはどうすればよいですか?

Unique_ptrメンバーを持つクラスがあります。

class Foo {
private:
    std::unique_ptr<Bar> bar;
    ...
};

バーは、create()関数とdestroy()関数を持つサードパーティクラスです。

スタンドアロン関数でstd::unique_ptrを使用したい場合は、次のようにします。

void foo() {
    std::unique_ptr<Bar, void(*)(Bar*)> bar(create(), [](Bar* b){ destroy(b); });
    ...
}

クラスのメンバーとしてstd::unique_ptrを使用してこれを行う方法はありますか?

110
huitlarc

createdestroyは、次のシグネチャを持つ無料の関数(OPのコードスニペットの場合のようです)であると仮定します。

Bar* create();
void destroy(Bar*);

このようにクラスFooを書くことができます

class Foo {

    std::unique_ptr<Bar, void(*)(Bar*)> ptr_;

    // ...

public:

    Foo() : ptr_(create(), destroy) { /* ... */ }

    // ...
};

destroyはすでに削除プログラムであるため、ここでラムダまたはカスタムの削除プログラムを記述する必要はありません。

108
Cassio Neri

C++ 11のラムダ(G ++ 4.8.2でテスト済み)を使用して、これをきれいに行うことができます。

この再利用可能なtypedefを考えると:

template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

あなたは書ける:

deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });

たとえば、FILE*の場合:

deleted_unique_ptr<FILE> file(
    fopen("file.txt", "r"),
    [](FILE* f) { fclose(f); });

これにより、try/catchノイズを必要とせずに、RAIIを使用した例外セーフクリーンアップの利点が得られます。

96
Drew Noakes

削除クラスを作成するだけです:

struct BarDeleter {
  void operator()(Bar* b) { destroy(b); }
};

そして、unique_ptrのテンプレート引数として提供します。コンストラクターでunique_ptrを初期化する必要があります。

class Foo {
  public:
    Foo() : bar(create()), ... { ... }

  private:
    std::unique_ptr<Bar, BarDeleter> bar;
    ...
};

私の知る限り、一般的なc ++ライブラリはすべてこれを正しく実装しています。 BarDeleterには実際には状態がないため、unique_ptrのスペースを占有する必要はありません。

52
rici

実行時に削除者を変更できるようにする必要がない限り、カスタムの削除者タイプを使用することを強くお勧めします。たとえば、削除プログラムに関数ポインターを使用する場合は、sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*)。つまり、unique_ptrオブジェクトのバイトの半分が無駄になります。

ただし、すべての機能をラップするカスタム削除機能を作成するのは面倒です。ありがたいことに、関数にテンプレート化された型を書くことができます:

C++ 17以降:

template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>;

template <typename T, auto fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;

// usage:
my_unique_ptr<Bar, destroy> p{create()};

C++ 17より前:

template <typename D, D fn>
using deleter_from_fn = std::integral_constant<D, fn>;

template <typename T, typename D, D fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<D, fn>>;

// usage:
my_unique_ptr<Bar, decltype(destroy), destroy> p{create()};
9
Justin

ご存知のように、カスタム削除ツールを使用することは、コード全体に言及する必要があるため、最善の方法ではありません。
代わりに、 特殊化の追加が許可されている場合::stdの名前空間レベルのクラスに、カスタム型が関係し、セマンティクスを尊重する限り、次のようにします。

特化 std::default_delete

template <>
struct ::std::default_delete<Bar> {
    default_delete() = default;
    template <class U, class = std::enable_if_t<std::is_convertible<U*, Bar*>()>>
    constexpr default_delete(default_delete<U>) noexcept {}
    void operator()(Bar* p) const noexcept { destroy(p); }
};

そして多分 std::make_unique()

template <>
inline ::std::unique_ptr<Bar> ::std::make_unique<Bar>() {
    auto p = create();
    if (!p) throw std::runtime_error("Could not `create()` a new `Bar`.");
    return { p };
}
4
Deduplicator

Destroy関数でstd::bindを使用するだけです。

std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy,
    std::placeholders::_1));

しかしもちろん、ラムダを使用することもできます。

std::unique_ptr<Bar, std::function<void(Bar*)>> ptr(create(), [](Bar* b){ destroy(b);});
3
mkaes