make_uniqueおよび完全な転送


216

std::make_unique標準C ++ 11ライブラリに関数テンプレートがないのはなぜですか?見つけた

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

少し冗長。次の方がずっといいと思いませんか?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

これはnew見事に非表示になり、タイプについては1回だけ言及します。

とにかく、ここに私の実装の試みがありますmake_unique

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

std::forwardコンパイルするのにかなり時間がかかりましたが、それが正しいかどうかはわかりません。それは...ですか?正確にはstd::forward<Args>(args)...どういう意味ですか?コンパイラはそれを何で作っていますか?


1
前にこの議論をしたことはかなり確かです...またunique_ptr、何とか許可する必要のある2番目のテンプレートパラメーターを受け取ることにも注意してください。これはとは異なりshared_ptrます。
Kerrek SB、2011

5
@Kerrek:make_uniqueカスタム削除を使用してパラメーター化しても意味がないと思います。明らかにプレーンオールドnewを介して割り当てられるため、プレーンオールドを使用する必要がありますdelete:)
fredoverflow

1
@フレッド:そうだね。だから、提案make_uniquenew割り当てに限定されます...まあ、あなたがそれを書きたいと思っても大丈夫ですが、そのようなものが標準の一部ではない理由を私は見ることができます。
Kerrek SB、2011

4
実際にはmake_unique、のコンストラクタstd::unique_ptrは明示的でありunique_ptr、関数から戻るのは冗長であるため、テンプレートを使用するのが好きです。また、私はではなく使用したいauto p = make_unique<foo>(bar, baz)ですstd::unique_ptr<foo> p(new foo(bar, baz))
Alexandre C.

回答:


157

C ++標準化委員会の委員長であるハーブサッターは、彼のブログに次のように書いています

C ++ 11に含まれていないのmake_uniqueは部分的な見落としであり、将来的にはほぼ確実に追加される予定です。

彼はまた、OPによって提供されるものと同じ実装を提供します。

編集: std::make_unique現在はC ++ 14の一部です。


2
make_unique関数テンプレートは、それ自体が例外安全な呼び出しを保証するものではありません。それは、呼び出し元がそれを使用するという慣例に依存しています。対照的に、厳密な静的型チェック(C ++とCの主な違い)は、型を介して安全性を強制するという考えに基づいて構築されています。そして、そのためには、make_unique関数ではなく単にクラスにすることができます。たとえば、2010年5月の私のブログ記事を参照してください。これは、Herbのブログに関するディスカッションからもリンクされています。
乾杯とhth。-Alf

1
@DavidRodríguez-dribeas:Herbのブログを読んで、例外的な安全性の問題を理解してください。「派生するように設計されていない」ことを忘れてください。それは単なるごみです。make_uniqueクラスを作るという私の提案の代わりに、私は今、それをを生成する関数にする方が良いと思いますmake_unique_t、その理由は、最もひどい構文解析の問題です:-)。
乾杯とhth。-Alf

1
Cheersandhth.-アルフ@:私はちょうど1ので、多分、我々は、別の記事を読んで持って読んで明確にはその述べmake_unique申し出強い例外保証を。または、ツールとその使用方法を混在させている可能性があります。その場合、例外的に安全な機能はありません。考えてみてvoid f( int *, int* ){}、明らかにno throw保証を提供しますが、誤用される可能性があるため、あなたの推論の系統では例外的に安全ではありません。さらに悪いことに、void f( int, int ) {}どちらか!:例外安全ではありませんtypedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
dribeas -デビッド・ロドリゲス・

2
@ Cheersandhth.-Alf:上記のよう make_unique実装された例外の問題について尋ねたところ、Sutterの記事(私のgoogle-fuが私に強い例外保証make_unique提供していると指摘した記事)を指摘しました。別の記事がある場合、私それを読みたいと思います。だから私の元の質問はどうやって(上で定義したように)例外安全ではないのですか?(追記:はい、私は思います向上、それが適用されることができる場所で例外安全性を)make_uniquemake_unique
デビッド・ロドリゲス- dribeas

3
...また、安全性の低いmake_unique機能を追求していません。最初に、これがどのように安全でないかはわかりません。また、型を追加して安全にする方法もわかりません。私が知っているのは、この実装が持つ可能性がある問題(私には何も見えない)と、代替の実装がそれらをどのように解決するかを理解することに興味があるということです。何であるの規則make_uniqueに依存しますか?安全を確保するために、型チェックをどのように使用しますか?これらは、私が回答を希望する2つの質問です。
DavidRodríguez-

78

いいですが、Stephan T. Lavavej(STLとして知られています)にはmake_unique、アレイバージョンで正しく機能するのより優れたソリューションがあります。

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

これは彼のCore C ++ 6ビデオで見ることができます。

make_uniqueのSTLのバージョンの更新バージョンがN3656として利用可能になりました。このバージョンドラフトC ++ 14に採用されました。


make_uniqueは、Stephen T Lavalejによって次のstdアップデートで提案されています。
tominator 2012年

これは彼がそれを追加することについて話しているようなものです。channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/...
tominator

不必要な編集をすべてxeoにする理由は、そのままで問題ありませんでした。そのコードは、Stephen T. Lavalejが記述したとおりであり、彼はstdライブラリを維持するdinkumwareで働いています。あなたはすでにその壁についてコメントしているので、知っておくべきです。
tominator

3
の実装make_uniqueはヘッダーに入れる必要があります。ヘッダーは名前空間をインポートすべきではありません(Sutter / Alexandrescuの「C ++ Coding Standards」の本のItem#59を参照してください)。Xeoの変更は、悪い習慣の奨励を回避するのに役立ちます。
Bret Kuhns

残念ながら、VC2010ではサポートされていません。VC2012でもサポートされていません。どちらもvaridic tempalteパラメーターをサポートしていません
zhaorufei

19

std::make_sharedは単にの省略形ではありませんstd::shared_ptr<Type> ptr(new Type(...));。それなしではできないことを行います。

その仕事std::shared_ptrをするために、実際のポインタのストレージを保持することに加えて、追跡ブロックを割り当てる必要があります。ただし、std::make_shared実際のオブジェクトを割り当てるため、同じメモリブロックにstd::make_sharedオブジェクトトラッキングブロックの両方を割り当てる可能性があります。

したがって、std::shared_ptr<Type> ptr = new Type(...);2つのメモリ割り当て(1つはnew、1つはstd::shared_ptrトラッキングブロック)である一方で、1つのメモリブロックをstd::make_shared<Type>(...)割り当てます。

これは、の多くの潜在的なユーザーにとって重要ですstd::shared_ptr。a std::make_uniqueが行う唯一のことは、もう少し便利です。それ以上のものはありません。


2
必須ではありません。ヒント付きですが、必須ではありません。
パピー

3
便宜のためだけでなく、特定のケースで例外の安全性も向上します。これについては、Kerrek SBの回答を参照してください。
Piotr99 2013年

19

独自のヘルパーを書くことを妨げるものは何もありませんがmake_shared<T>、ライブラリで提供する主な理由は、実際にはとは異なる内部型の共有ポインターを作成するshared_ptr<T>(new T)ためです。ヘルパー。

あなたのmake_unique一方、ラッパーは、周りの単なるシンタックスシュガーであるnew、それは目に喜ば見えるかもしれないが、そう、それは何も持っていない、表現newのテーブルに。 訂正:これは実際には当てはまりません。new式をラップする関数呼び出しがあると、たとえば関数を呼び出す場合など、例外的な安全性が得られますvoid f(std::unique_ptr<A> &&, std::unique_ptr<B> &&)new互いにシーケンスされていない2つのraw があると、1つの新しい式が例外で失敗した場合、もう1つの式がリソースをリークする可能性があります。なぜmake_unique標準がないのかについては、それは単に忘れられていただけです。(これは時々発生します。グローバルstd::cbeginがあるはずですが、標準にはありません。)

また、これunique_ptrは、何らかの理由で許可する必要がある2番目のテンプレートパラメータをとります。これはshared_ptr、型消去を使用してカスタム削除者を型の一部にすることなく格納するとは異なります。


1
@FredOverflow:共有ポインターは比較的複雑なクラスです。内部的には、ポリモーフィック参照制御ブロックを保持しますが、いくつかの異なる種類の制御ブロックがあります。shared_ptr<T>(new T)それらの1つをmake_shared<T>()使用し、別のものを使用します。それを許可することは良いことであり、make-sharedバージョンはある意味であなたが得ることができる最も軽量の共有ポインタです。
Kerrek SB、2011

15
@FredOverflow:shared_ptr動的メモリのブロックを割り当てて、を作成するときにカウントと「ディスポーザ」アクションを維持しますshared_ptr。ポインタを明示的に渡す場合、「新しい」ブロックを作成する必要があります。make_sharedこれを使用するとオブジェクトと衛星データを単一のメモリブロック(1 new)にバンドルできるため、割り当て/割り当て解除が速くなり、断片化が少なくなり、通常)より良いキャッシュ動作。
Matthieu M.


4
-1「もう一方のmake_uniqueラッパーは、新しい式の周りの単なる構文上の糖衣なので、見た目は楽しいかもしれませんが、テーブルには何も新しいものはありません。」間違っている。これは、例外安全な関数呼び出しの可能性をもたらします。ただし、保証はありません。そのため、正式な引数をそのクラスで宣言できるように、クラスである必要があります(Herbはこれをオプトインとオプトアウトの違いとして説明しています)。
乾杯とhth。-アルフ

1
@ Cheersandhth.-Alf:そうだね。私はこれに気づきました。答えを編集します。
Kerrek SB、

13

C ++ 11では...(テンプレートコードで)「パック拡張」にも使用されます。

要件は、それをパラメーターの展開されていないパックを含む式のサフィックスとして使用することであり、パックの各要素に式を適用するだけです。

たとえば、あなたの例に基づいて構築します:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

後者は正しくないと思います。

また、引数のパックを展開されていない関数に渡すことはできません。テンプレートパラメータのパックについては不明です。


1
これがスペルアウトされているのを見てうれしいです。variadicテンプレートパラメータも含めないのはなぜですか?
Kerrek SB、2011

@Kerrek:奇妙な構文についてはよくわからないので、多くの人が遊んだかどうかわかりません。だから私は知っていることを守ります。誰かがやる気と知識が十分にある場合は、可変長構文が非常に完全であるため、c ++ FAQエントリが必要になる場合があります。
Matthieu M.

構文はstd::forward<Args>(args)...であり、に展開されforward<T1>(x1), forward<T2>(x2), ...ます。
Kerrek SB、2011

1
forward実際には常にテンプレートパラメータが必要だと思いませんか?
Kerrek SB、2011

1
@ハワード:そうです!警告(インスタンスをトリガ強制ideone.com/GDNHb
マシューM.

5

Stephan T. Lavavejの実装に触発されて、配列の範囲をサポートするmake_uniqueがあればいいのではないかと思いました。それはgithubにあり、コメントが欲しいです。これを行うことができます:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.