2016-11-06 11 views
20

This answerstd::shared_ptrに対する今後の変更の両方T[]T[N]変異体を可能にすることを示している、N4082を引用:アレイのunique_ptr部分特殊異なりshared_ptrを許可する理由<T[N]>?

shared_ptr<T[]>shared_ptr<T[N]>の両方が有効になり、両方がdelete[]あることになりオブジェクトの管理された配列で呼び出されます。

template<class Y> explicit shared_ptr(Y* p); 

が必要です:Yは完全な型でなければなりません。配列が配列タイプの場合、配列タイプでない場合はとなります。Tは整形式で、十分に定義された振る舞いを持ち、例外をスローしないものとします。 TU[N]の場合、Y(*)[N]T*に変換可能です。 TU[]の場合、Y(*)[]T*に変換可能です。そうでなければ、Y*T*に変換可能とする。

私が間違ってる場合を除き、Y(*)[N]は明確shared_ptrが所有または削除することはできません、配列のアドレスを取ることによって形成することができます。また、Nが管理対象オブジェクトのサイズをに適用するという表示はありません。

T[N]の構文を可能にする背景には何がありますか?それは実際の利益をもたらしますか?あれば、どのように使用されますか?

+3

あなたの悪用を防ぐものはありませんが、一度 'shared_ptr 'をお持ちでしたら、追加情報なしでその要素を反復処理することができます。 'new T [N]'からポインタを取得しようとしているので、最初の要素へのポインタでそれを構築します。 –

+0

@KerrekSB "... *新しい*' T [N] 'からポインタを取得すると思われるので、必ずしもそうではありません。 [私の答え](http://stackoverflow.com/a/40447905/6394138)を参照してください。 – Leon

+0

さらにN4082を見てきましたが、 'operator []'は 'i

答えて

2

私が間違ってる場合を除き、Y(*)[N]は明確shared_ptrが所有または削除することはできません、配列のアドレス を取ることによって形成することができます。

shared_ptrは、一般的なリソース管理ユーティリティで、カスタムデアロケータを構築することができるということを忘れないでください:

template<class Y, class D> shared_ptr(Y* p, D d); 

このようなユーザー提供のデアロケータはdelete/delete[]以外のアクションを実行することができます。たとえば、問題の配列がファイル記述子の配列である場合、「デアロケータ」はそれらのすべてを閉じることができます。

このような場合、shared_ptrは広く使用されている意味でオブジェクトを所有していないため、そのアドレスを取ることによって既存の配列にバインドすることができます。

+0

"shared_ptr"が一般的なリソース管理ユーティリティであることを正当化するテキストが標準にありますか?私が常に理解しているように、 'shared_ptr'は所有権の契約を意味します。カスタム・リターダーを許可する必要があるということは、契約を厳格に強制することはできませんが、私はいつも、所有していない 'shared_ptr'を持つことはエラーとして見てきました。 –

+1

@ monkey_05_06カスタムデアロケータのサポートにより、汎用のリソース管理ユーティリティになります。オブジェクトを参照する最後のポインタがクリーンアップアクションを実行する共有ポインタについて知っているだけです(オブジェクトの削除はデフォルトですが、基本的には何でも構いません)。 – Leon

+0

それはセマンティクスの議論かもしれませんが、 'std :: get_deleter'のようなメソッドの存在は、' delete'演算が 'shared_ptr'の契約部分であることを示唆しています(' operator delete'がデフォルトのDeleter)。あなたが所有していない 'shared_ptr'を作り出すことができるかどうか議論するつもりはありませんが、そうすることは実際にやるのは悪いことです。私は 'shared_ptr'を参照ラッパーとして使用するコードを難読化していると感じています。 –

6

所有権を共有するネストされたオブジェクトへのポインタを、包含するオブジェクトにstd::shared_ptrで渡すことができます。このネストされたオブジェクトが配列であることを起こる、あなたは配列型としてアクセスしたい場合は、実際には、適切なTNT[N]を使用する必要があります。上記のコードq

#include <functional> 
#include <iostream> 
#include <iterator> 
#include <memory> 
#include <queue> 
#include <utility> 
#include <vector> 

using queue = std::queue<std::function<void()>>; 

template <typename T> 
struct is_range { 
    template <typename R> static std::false_type test(R*, ...); 
    template <typename R> static std::true_type test(R* r, decltype(std::begin(*r))*); 
    static constexpr bool value = decltype(test(std::declval<T*>(), nullptr))(); 
}; 

template <typename T> 
std::enable_if_t<!is_range<T>::value> process(T const& value) { 
    std::cout << "value=" << value << "\n"; 
} 

template <typename T> 
std::enable_if_t<is_range<T>::value> process(T const &range) { 
    std::cout << "range=["; 
    auto it(std::begin(range)), e(std::end(range)); 
    if (it != e) { 
     std::cout << *it; 
     while (++it != e) { 
      std::cout << ", " << *it; 
     } 
    } 
    std::cout << "]\n"; 
} 

template <typename P, typename T> 
std::function<void()> make_fun(P const& p, T& value) { 
    return [ptr = std::shared_ptr<T>(p, &value)]{ process(*ptr); }; 
          // here ----^ 
} 

template <typename T, typename... M> 
void enqueue(queue& q, std::shared_ptr<T> const& ptr, M... members) { 
    (void)std::initializer_list<bool>{ 
     (q.push(make_fun(ptr, (*ptr).*members)), true)... 
     }; 
} 

struct foo { 
    template <typename... T> 
    foo(int v, T... a): value(v), array{ a... } {} 
    int value; 
    int array[3]; 
    std::vector<int> vector; 
}; 

int main() { 
    queue q; 
    auto ptr = std::make_shared<foo>(1, 2, 3, 4); 
    enqueue(q, ptr, &foo::value, &foo::array, &foo::vector); 
    while (!q.empty()) { 
     q.front()(); 
     q.pop(); 
    } 
} 

だけのシンプルなstd::queue<std::function<void()>>ですが、私はあなたが別のスレッドに処理をオフロードするスレッドプールであるかもしれないと想像できることを願っています。実際にスケジュールされた処理も簡単ですが、実際にはかなりの量の作業であると想像していただければ幸いです。

+1

ここであなたの例の一部を見逃してしまったかもしれませんが、この例では 'shared_ptr 'がなぜ必要なのか分かりません。この例は 'shared_ptr 'と 'shared_ptr '(後者は 'std :: experimental :: shared_ptr'と仮定しています)と同様に動作しますが、' f-> array'の代わりにそのアドレスを '&'で置き換えます。とにかく 'int(*)[42]'が関数呼び出しにマッチする 'element_type *'(つまり 'int *')に崩壊しなければならないとしても、明らかな利点はありません... –

+0

@ monkey_05_06:for具体的なコードでは、アドレス演算子を簡単に削除できます。ジェネリックコードでは、配列を扱うことは簡単に避けることができる特別なケースです。 –

+0

ジェネリックコードによって住所演算子を削除するのが簡単になる例を表示できますか?配列の崩壊は暗黙的なものなので、 'T(*)[N]'と 'T *'の区別をすることが有用な例は考えられません。この場合、 'N 'は' T [N] 'の特殊化であってもどこにでも明示的に**保存されていないので、' N'が 'size'パラメータに転送されているという引数さえもできません(何もないので)。 –

関連する問題