C ++での完党な送信ずナニバヌサルリンク

最近、Eli Benderskyによる蚘事「C ++での完党な転送ず普遍的な参照」ぞのリンクがisocpp.orgで公開されたした。 この短い蚘事には、単玔な質問に察する単玔な答えがありたす-どのタスクず右蟺倀リンクの䜿甚方法に぀いお。



プログラムの効率を高めるこずを目的ずするC ++ 11の革新の1぀は、STLコンテナヌのメ゜ッドのプレヌスファミリです。 たずえば、std :: vectorでは、emplace_backメ゜ッドほがpush_backメ゜ッドの類䌌ずemplaceメ゜ッドほがinsertメ゜ッドの類䌌が登堎したした。

これらの新しいメ゜ッドの目的を瀺す小さな䟋を次に瀺したす。

class MyKlass { public: MyKlass(int ii_, float ff_) {...} private: {...} }; some function { std::vector<MyKlass> v; v.push_back(MyKlass(2, 3.14f)); v.emplace_back(2, 3.14f); }
      
      





MyKlassクラスのコンストラクタヌずデストラクタヌの呌び出しに埓うず、push_back呌び出し䞭に次のこずがわかりたす。





ご芧のように、push_backメ゜ッドに枡されるオブゞェクトは明らかに右蟺倀参照であり、この匏が実行された盎埌に砎棄されるため、非垞に倚くの䜜業が行われおいたすが、その倚くはそれほど必芁ありたせん。 したがっお、䞀時オブゞェクトを䜜成および砎棄する理由はありたせん。 この堎合、ベクタヌのすぐ内偎にオブゞェクトを䜜成しないのはなぜですか これはたさにemplace_backメ゜ッドが行うこずです。 䟋v.emplace_back2、3.14fの匏では、1぀のコンストラクタヌのみが実行され、ベクタヌ内にオブゞェクトが䜜成されたす。 䞀時オブゞェクトを䜿甚したせん。 emplace_back自䜓がMyKlassのコンストラクタヌを呌び出し、必芁な匕数をそれに枡したす。 この動䜜は、C ++ 11の2぀の革新によっお可胜になりたした。可倉数の匕数を持぀テンプレヌト可倉アドむンテンプレヌトず理想的な送信完党転送です。 この蚘事では、完璧な䌝送がどのように機胜し、どのように䜿甚するかを説明したす。



完党な䌝送問題



タむプE1、E2、...、Enのパラメヌタヌを受け取るfunc関数があるずしたす。 同じパラメヌタヌのセットを受け入れるラッパヌ関数を䜜成する必芁がありたす。 蚀い換えるず、䞀時倉数を䜜成せずに受信したパラメヌタヌを別の関数に枡す関数を定矩する、぀たり理想的な転送を実行したす。

問題を特定するには、䞊蚘で説明したemplace_backメ゜ッドを怜蚎しおください。 vector :: emplace_backは、Tが䜕であるかを䜕も知らずに、そのパラメヌタヌをTコンストラクタヌに枡したす。

次のステップでは、C ++ 11の革新を䜿甚せずにこの動䜜を実珟する方法を瀺すいく぀かの䟋を怜蚎したす。 簡単にするために、可倉数の匕数パラメヌタヌを持぀テンプレヌトを䜿甚する必芁性を考慮したせん; 2぀の匕数のみが必芁であるず仮定したす。

頭に浮かぶ最初のオプション

 template <typename T1, typename T2> void wrapper(T1 e1, T2 e2) { func(e1, e2); }
      
      





しかし、ラッパヌは倀によっおパラメヌタヌを受け入れるため、funcが参照によっおパラメヌタヌを受け入れる堎合、これは明らかに必芁に応じお機胜したせん。 この堎合、funcが参照によっお受け取ったパラメヌタヌを倉曎しおも、ラッパヌに枡されるパラメヌタヌには圱響したせんラッパヌ内で䜜成されたコピヌは倉曎されたす。

それでは、参照によっおパラメヌタヌを受け入れるようにラッパヌをやり盎すこずができたす。 funcが参照ではなく倀で受け入れられる堎合、これは障害になりたせん。ラッパヌ内のfuncはそれ自䜓に必芁なコピヌを䜜成するためです。

 template <typename T1, typename T2> void wrapper(T1& e1, T2& e2) { func(e1, e2); }
      
      





別の問題がありたす。 右蟺倀を参照ずしお関数に枡すこずはできたせん。 したがっお、完党に簡単な呌び出しはコンパむルされたせん。

 wrapper(42, 3.14f); // :    rvalue- wrapper(i, foo_returning_float()); //   
      
      





そしお、これらのリンクを䞀定にするずいうアむデアが生たれた堎合、すぐに問題は解決したせん。 funcでは、パラメヌタヌずしお非定数の参照が必芁になる堎合があるためです。

残っおいるのは、いく぀かのラむブラリで䜿甚されおいる粗雑なアプロヌチです䞀定の非定数リンクの関数をオヌバヌロヌドするには

 template <typename T1, typename T2> void wrapper(T1& e1, T2& e2) { func(e1, e2); } template <typename T1, typename T2> void wrapper(const T1& e1, T2& e2) { func(e1, e2); } template <typename T1, typename T2> void wrapper(T1& e1, const T2& e2) { func(e1, e2); } template <typename T1, typename T2> void wrapper(const T1& e1, const T2& e2) { func(e1, e2); }
      
      





指数関数的成長。 実際の関数のいく぀かの合理的な量のパラメヌタヌを凊理する必芁があるずきに、どれだけの楜しみがもたらされるか想像できたす。 さらに悪いこずに、C ++ 11は右蟺倀リンクを远加したすが、これもラッパヌ関数で考慮する必芁があり、これは明らかに拡匵可胜な゜リュヌションではありたせん。



右蟺倀リンクのリンク圧瞮ず特別な型掚論



C ++ 11で完党な転送がどのように実装されるかを説明するには、たずこのプログラミング蚀語に远加された2぀の新しいルヌルを理解する必芁がありたす。

簡単なものから始めたしょう-リンクの厩壊。 ご存知のように、C ++でのリンクぞのリンクの取埗は蚱可されおいたせんが、テンプレヌトが実装されおいる堎合にこれが発生するこずがありたす。

 template <typename T> void baz(T t) { T& k = t; }
      
      





この関数を次のように呌び出すずどうなりたすか

 int ii = 4; baz<int&>(ii);
      
      





テンプレヌトをむンスタンス化するずき、Tはintに蚭定されたす。 関数内の倉数kはどの型になりたすか コンパむラヌはintを「参照」したすが、これは犁止された構造であるため、コンパむラヌはこれを通垞のリンクに倉換するだけです。 実際、C ++ 11以前では、この動䜜は暙準化されおいたせんでしたが、倚くのコンパむラはメタプログラミングでよく芋られるため、そのようなコヌドを受け入れお倉換したした。 C ++ 11で右蟺倀リンクが远加された埌、さたざたなタむプのリンクを組み合わせるずきの動䜜を決定するこずが重芁になりたしたたずえば、int &&??の意味。

そこで、リンク圧瞮のルヌルが登堎したした。 このルヌルは非垞に単玔です-1぀のアンパサンドが垞に優先されたす。 したがっお-ずは、&&ず、およびず&&です。 圧瞮の結果が&&になるのは&&および&&のみです。 このルヌルは、が1で&&が0の論理OR結果ず比范できたす。

このトピックに盎接関連するもう1぀のC ++の远加は、さたざたな堎合の右蟺倀リンクの特別な型の掚論芏則です[1]。 テンプレヌト関数の䟋を考えおみたしょう。

 template <class T> void func(T&& t) { }
      
      





二重アンパサンドにだたされないでください。ここではtは右蟺倀参照ではありたせん[2]。 この状況で衚瀺される堎合特別な型掚論が必芁な堎合、T &&は特別な意味を持ちたす-funcがむンスタンス化されるず、Tは枡される型に応じお倉化したす。 タむプUの巊蟺倀が枡された堎合、TはUになりたす。 Uが右蟺倀の堎合、TはちょうどUになりたす。䟋

 func(4); // 4  rvalue: T  int double d = 3.14; func(d); // d  lvalue; T  double& float f() {...} func(f()); // f()  rvalue; T  float int bar(int i) { func(i); // i  lvalue; T  int& }
      
      





この芏則は珍しく、奇劙にさえ思えるかもしれたせん。 そうです。 しかし、それにもかかわらず、このルヌルが完党な送信の問題を解決するのに圹立぀こずを理解するこずになるず、このルヌルは非垞に明癜になりたす。



std :: forwardを䜿甚しお完党な転送を実装する



次に、䞊蚘のラッパヌテンプレヌト関数に戻りたしょう。 C ++ 11を䜿甚しお実装する方法は次のずおりです。

 template <typename T1, typename T2> void wrapper(T1&& e1, T2&& e2) { func(forward<T1>(e1), forward<T2>(e2)); }
      
      





そしお、ここに[3]がどのように実装されおいるかを瀺したす。

 template<class T> T&& forward(typename std::remove_reference<T>::type& t) noexcept { return static_cast<T&&>(t); }
      
      





次の呌び出しを怜蚎しおください。

 int ii ...; float ff ...; wrapper(ii, ff);
      
      





最初の匕数2番目も同様を考えおみたしょうiiは巊蟺倀であるため、T1は特別な型掚論の芏則に埓っおintになりたす。 結果はfuncの呌び出しですforward <int>e1、...。 したがっお、フォワヌドパタヌンはint型によっおむンスタンス化され、この関数の次のバヌゞョンを取埗したす。

 int& && forward(int& t) noexcept { return static_cast<int& &&>(t); }
      
      





リンク圧瞮ルヌルを適甚する時間

 int& forward(int& t) noexcept { return static_cast<int&>(t); }
      
      





蚀い換えれば、匕数は、巊蟺倀の芁求に応じお、参照によっおfuncに枡されたす。

次の䟋

 wrapper(42, 3.14f);
      
      





ここで、匕数は右蟺倀であるため、T1はintになりたす。 funcを呌び出したすforwarde1、...。 したがっお、フォワヌドテンプレヌト関数はint型によっおむンスタンス化され、次のバヌゞョンの関数を取埗したす。

 int&& forward(int& t) noexcept { return static_cast<int&&>(t); }
      
      





参照によっお取埗された匕数は、右蟺倀参照に倉換されたす。これは、前方から受け取るために必芁なものです。

Tが入力匕数のタむプ巊蟺倀たたは右蟺倀に応じお倀UたたはU &&をずるこずができる堎合、forwardテンプレヌト関数はstatic_cast <T &&>tの䞀皮のラッパヌず芋なすこずができたす。 ラッパヌは、匕数タむプの任意の組み合わせを凊理する1぀のテンプレヌトです。

フォワヌドテンプレヌト関数は、C ++ 11のstd名前空間のヘッダヌファむル「ナヌティリティ」に実装されおいたす。



別の泚意点std :: remove_referenceを䜿甚したす。 実際、この関数を䜿甚せずにforwardを実装できたす。 リンク圧瞮はすべおの䜜業を行うので、std :: remove_referenceを䜿甚するこずはこのために冗長です。 ただし、この関数を䜿甚するず、このタむプを掚枬できない状況C ++暙準14.8.2.5によるでTtを出力できるため、std :: forwardを呌び出すずきにテンプレヌトパラメヌタヌを明瀺的に指定する必芁がありたす。



ナニバヌサルリンク



圌のスピヌチ、ブログ投皿、および本の䞭で、Scott Myersは、型掚論のコンテキストにある右蟺倀リンクに「ナニバヌサルリファレンス」ずいう名前を付けおいたす。 この名前が成功したかどうかを蚀うのは難しいです。 私に関しおは、新しい本、Effective C ++からこのトピックに関する章を初めお読んだずき、混乱したした。 倚かれ少なかれ、基瀎ずなるメカニズムリンク圧瞮ず特別な型掚論芏則を理解したずきに、すべおが明らかになりたした。

キャッチは、「普遍的な参照」[4]ずいうフレヌズは、「型掚論の文脈における右蟺倀リンク」よりも確かに簡朔で矎しいずいうこずです。 ただし、コヌドを本圓に理解したい堎合は、完党な説明を避けるこずはできたせん。



完党な䌝送の䟋



完党な䌝送は、より高いレベルでのプログラミングを可胜にするため、非垞に䟿利です。 高階関数は、他の関数を匕数ずしお䜿甚したり、それらを返すこずができる関数です。 完党なパスがないず、ラッパヌ関数内の関数に匕数を枡す䟿利な方法がないため、高次関数を適甚するこずは非垞に負担になりたす。 ここでの「関数」ずいう甚語は、関数自䜓に加えお、コンストラクタヌが実際には関数でもあるクラスも意味したす。

この蚘事の冒頭で、emplace_backコンテナヌメ゜ッドに぀いお説明したした。 別の良い䟋は、 前の蚘事で説明した暙準テンプレヌト関数make_uniqueです。

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





その蚘事では、奇劙な二重アンパサンドを単に無芖し、可倉数のテンプレヌト匕数に焊点を合わせたこずを正盎に認めおいたす。 しかし、今ではコヌドを完党に理解するのは完党に簡単です。 蚀うたでもなく、理想的なパスず可倉数の匕数を持぀テンプレヌトは非垞に頻繁に䞀緒に䜿甚されたす。ほずんどの堎合、これらの匕数を枡す関数たたはコンストラクタヌがいく぀の匕数を受け入れるかわからないからです。

理想的な転送をはるかに耇雑に䜿甚した䟋ずしお、std :: bindの実装を芋るこずができたす。



゜ヌスぞの参照



玠材の準備で私を倧いに助けたいく぀かの情報源は次のずおりです。

  1. Bjarne Stroustrupによる「The C ++ Programming Language」の第4版
  2. Scott Myersによる新しい「Effective Modern C ++」。 この本では、「ナニバヌサルリンク」に぀いお詳しく説明しおいたす。 実際、この本の5分の1以䞊がこのトピックに圓おられおいたす。
  3. テクニカルペヌパヌn1385 「転送の問題匕数。」
  4. Thomas Becker C ++ Rvalueのリファレンスの説明 -優れた蚘述ず非垞に有甚な蚘事


泚

[1] autoおよびdecltypeも䜿甚できたす。ここでは、テンプレヌトを䜿甚する堎合のみを説明したす。

[2]右蟺倀リンクオヌバヌロヌド&&の衚蚘法を遞択しないずいうC ++暙準化委員䌚の決定が倱敗したず考えたす。 スコット・マむダヌズは圌のスピヌチの䞭で認めたそしお圌のブログに少しコメントした3幎埌、この資料はただ孊ぶのが簡単ではないこずを認めた。 そしお、「The C ++ Programming Language」の第4版のBjörnStraustrupは、std :: forwardを説明するずきに、テンプレヌト匕数の明瀺的な指瀺を忘れおいたした。 これは確かにかなり難しい領域であるず結論付けるこずができたす。

[3]これは、STL C ++ 11から転送されたstd ::の簡易バヌゞョンです。 右蟺倀匕数のために明瀺的にオヌバヌロヌドされた远加バヌゞョンがただありたす。 私はただそれが必芁な理由を解明しようずしおいたす。 䜕かアむデアがあれば教えおください。

[4]転送参照は、私が出くわした別の指定です。



翻蚳者からCppCon2014では、倚くの人マむダヌズ、ストラりストルプ、サファヌを含むが、 ナニバヌサル参照ではなく転送参照ずいう甚語を䜿甚するこずにしたした。



このトピックに関するhabrに関する蚘事

右蟺倀リンクの簡単な玹介

C ++ 11たたはT &&の「ナニバヌサル」参照は、必ずしも「右蟺倀参照」を意味するわけではありたせん



All Articles