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


133

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

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

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

std::unique_ptrスタンドアロン関数でwith it を使用したい場合、次のことができます。

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

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

回答:


133

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

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

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

class Foo {

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

    // ...

public:

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

    // ...
};

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


156
C ++ 11を使用std::unique_ptr<Bar, decltype(&destroy)> ptr_;
Joe

1
このソリューションの欠点は、すべてのオーバーヘッドが2倍になることですunique_ptr(すべて実際のデータへのポインターと共に関数ポインターを格納する必要があります)、毎回破棄関数を渡す必要があるため、インライン化できません(テンプレートではできないため)特定の関数に特化し、シグネチャのみ)、ポインタを介して関数を呼び出す必要があります(直接呼び出しよりもコストがかかります)。riciDeduplicatorのどちら回答も、ファンクタに特化することでこれらのコストをすべて回避します。
ShadowRanger

@ShadowRangerは、明示的に渡すかどうかにかかわらず、毎回default_delete <T>およびストアドファンクションポインターに定義されていませんか?
Herrgott

117

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

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


2
これが答えになるはずです、イモ。より美しいソリューションです。それとも、たとえばstd::function定義などにあるようなマイナス面はありますか?
j00hi

17
@ j00hi、私の意見では、このソリューションはのために不要なオーバーヘッドを持っていstd::functionます。承認された回答のラムダまたはカスタムクラスは、このソリューションとは異なりインライン化できます。ただし、この方法は、すべての実装を専用モジュールに分離したい場合に有利です。
magras

5
std :: functionコンストラクターがスローすると、メモリがリークします(ラムダが大きすぎてstd :: functionオブジェクト内に収まらない場合に発生する可能性があります)
StaceyGirl

4
ラムダは本当にここに必要ですか?規則に従うdeleted_unique_ptr<Foo> foo(new Foo(), customdeleter);場合customdeleterは簡単です(それはvoidを返し、生のポインターを引数として受け入れます)。
Victor Polevoy 2017

このアプローチには1つの欠点があります。std :: functionは、可能な限りmoveコンストラクターを使用する必要はありません。これは、std :: move(my_deleted_unique_ptr)を実行すると、lambdaで囲まれたコンテンツが移動ではなくコピーされる可能性があることを意味します。
GeniusIsme 2017

70

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

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


8
このオプションは、ゼロパラメーターstd :: unique_ptrコンストラクターを使用できるため、配列、std :: vector、およびその他のコレクションで機能する唯一のオプションです。他の回答では、一意のポインターを作成するときにDeleterインスタンスを提供する必要があるため、このゼロパラメーターコンストラクターにアクセスできないソリューションを使用します。ただし、このソリューションは、コンストラクターがDeleterインスタンスを独自に作成できるようにするDeleterクラス(struct BarDeleter)からstd::unique_ptrstd::unique_ptr<Bar, BarDeleter>)を提供しstd::unique_ptrます。つまり、次のコードが許可されますstd::unique_ptr<Bar, BarDeleter> bar[10];
DavidF

12
簡単に使用できるtypedefを作成しますtypedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtr
DavidF

@DavidF:または使用デュプリケータのアプローチと同じ利点がある、(インライン化削除、それぞれの余分なストレージはunique_ptr、必要が構築するときデリータのインスタンスを提供しないため)、および使用することができるという利点追加std::unique_ptr<Bar>を覚えておくことが必要とすることなく、どこでも特別な、typedefまたは明示的に提供する2番目のテンプレートパラメータを使用します。(明確にするために、これは良い解決策です、私は賛成票を投じましたが、シームレスな解決策に一歩恥ずかしがります)
ShadowRanger

22

実行時に削除を変更できるようにする必要がない限り、カスタムの削除タイプを使用することを強くお勧めします。たとえば、あなたのデリータの関数ポインタを使用している場合、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()};

気の利いた。これは、ボイラープレートが少ないだけで、riciの回答からファンクターと同じ利点(メモリオーバーヘッドが半分になり、関数ポインターを介さずに関数を直接呼び出す、関数の呼び出しが完全にインライン化される可能性がある)を達成することを正しいと思いますか?
ShadowRanger

はい、これでカスタム削除クラスのすべての利点が得られますdeleter_from_fn
rmcclellan

6

単にstd::binddestroy関数で使用できます。

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

6

コード全体で言及する必要があるため、カスタムの削除機能を使用するのは最善の方法ではありません。
代わりに、カスタムタイプが関係し、セマンティクスを尊重している限り、名前空間レベルのクラスに特殊化を追加することが許可されているため::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 };
}

2
これには非常に注意が必要です。アップ開くとstdワームの全く新しい缶を開きます。またstd::make_unique、C ++ 20 stdはクラステンプレートではないもの(std::make_unique関数テンプレート)の特殊化を許可しないため、C ++ 20の特殊化は許可されていません(したがって、前に行うことはできません)。渡されたポインタがからでstd::unique_ptr<Bar>はなくcreate()、他の割り当て関数から割り当てられた場合も、おそらくUBになることに注意してください。
ジャスティン

これが許可されているとは思いません。この特殊化std::default_deleteが元のテンプレートの要件を満たしていることを証明するのは難しいように思えます。私はそれstd::default_delete<Foo>()(p)がを書くdelete p;ための有効な方法であると想像するので、もし書くdelete p;ことが有効であるならば(つまり、もしFoo完全であれば)、これは同じ振る舞いではないでしょう。さらに、delete p;が書き込みに無効である(Foo不完全である)場合、これは動作をstd::default_delete<Foo>同じに保つのではなく、の新しい動作を指定することになります。
ジャスティン

make_unique専門は問題があるが、私は間違いなく使用していたstd::default_delete過負荷(とテンプレートではないenable_ifだけにOpenSSLのようなCの構造体のために、BIGNUMサブクラス化が起こるだろうされていない既知の破壊機能を使用)、そしてそれは、前述したように、これまで最も簡単な方法でですコードの残りの部分ではunique_ptr<special_type>、テンプレートとしてファンクターの型を渡さDeleterなくても使用できます。また、typedef/ usingを使用して型に名前を付け、その問題を回避することもできます。
ShadowRanger
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.