スコットマむダヌズの効果的なモダンC ++ぞの泚釈

数ヶ月前、Scott Meyersは新しい本、 Effective Modern C ++をリリヌスしたした。 近幎、圌は間違いなく「これに぀いお」ラむタヌNo. 1であり、さらに圌は玠晎らしい講垫であり、圌の新しい本はそれぞれC ++ラむタヌによっお読たれる運呜にありたす。 さらに、私は長い間このような本を埅っおいたした、C ++ 11暙準が出お、C ++ 14が続き、C ++ 17の前にすでに蚀語が急速に倉化しおいるこずがわかりたすが、すべおの倉曎が䞀般的に説明されおいる堎所はありたせんそれら、危険な堎所、掚奚されるパタヌン。



それでも、Habrを定期的に芋お、新しい本に関する出版物を芋぀けられたせんでした。自分で曞く必芁があるようです。 もちろん、私は完党な翻蚳には十分ではないので、簡単に絞り、控えめに泚釈ず呌ぶこずにしたした。 私はたた、玠材を再線成する自由を取りたした、この順序の短い蚀い盎しがより良いように思えたす。 すべおのコヌド䟋は本から盎接匕甚されおおり、時々远加されおいたす。



1぀の譊告Myers は構文を説明しおいたせん。読者がキヌワヌド、ラムダ匏の曞き方などを知っおいるず想定されおいたす 。 したがっお、誰かがこの本からC ++ 11/14の孊習を開始するこずに決めた堎合、参照甚に远加資料を䜿甚する必芁がありたす。 ただし、これは問題ではなく、ワンクリックですべおがグヌグルになりたす。



C ++ 98からC ++ 11/14ぞ。 すべおのニュヌスのギャロップ



自動 -䞀芋するず、それは単なる倧きな砂糖のスプヌンにすぎたせんが、本質ではないにしおも、C ++コヌドの圢匏を倉えるこずができたす。 Straustrupは、1983幎に珟圚の意味でこのキヌワヌド具䜓的だがCでは無甚を導入しようずしおいたが、Cコミュニティからの圧力の䞋でこの考えを攟棄したこずが刀明した。 これによりコヌドがどのように倉曎されるかを確認しおください。



template<typename It> void dwim(It b, It e) { while(b != e) { typename std::iterator_traits<It>::value_type value=*b; .... } } template<typename It> void dwim(It b, It e) { while(b != e) { auto value=*b; ... } }
      
      





2番目の䟋は単に短いだけでなく、ここでは、完党に䞍芁な正確な匏* bを非衚瀺にしたす。 さらに、本質的に、匏std :: iterator_traits <It> :: value_typeは、参照解陀時に取埗されるむテレヌタヌのタむプを決定するためにSTLのd明期に発明された独創的な束葉杖にすぎたせん。ただし、2番目の唯䞀の挔算子には*が必芁です。 束葉杖でダりン



玍埗できない ここに別の䟋がありたすが、私の意芋では単なるキラヌです



 std::unorderd_map<std::string,int> m; for(std::pair<std::string,int>& p : m) { ... }
      
      





このコヌドはコンパむルされたせん。

蚌拠
auto1.cc:8:38゚ラヌタむプstd ::ペア<std :: basic_string <char>、int>の参照の無効な初期化タむプstd ::ペア<const std :: basic_string <char>の匏から、 int>

、事実は、std :: unordered_map <std :: string、int>の正しい型はstd :: pair < const std :: string、int>であるずいうこずです。キヌは定数でなければならないのは明らかですが、 autoを䜿甚する方が正確であるよりもはるかに簡単です頭の䞭の衚珟のタむプ。

蚀語に厳密さを远加するいく぀かのポむント



 int x1=1; //1  int x2; //2    ! auto x3=1; //3  auto x4; //4 !    std::vector<int> v; unsigned x5=v.size(); //5   size_t,    auto x6=v.size(); //6  int f(); int x7=f(); //7     f() ? auto x8=f(); //8 
      
      





これらの䟋からわかるように、 autoを䜓系的に䜿甚するず、デバッグ時に倚くの神経を節玄できたす。



そしお最埌に、 autoがなければ、ラムダ匏は単に䞍可胜です



 auto derefUPLess= [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };
      
      





この堎合、正確な型derefUPLessはコンパむラヌのみが知っおおり、 autoを䜿甚せずに倉数に栌玍するこずはできたせん。 もちろん、次のように曞くこずもできたす。



 std::function<bool (const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> derefUPLess= [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; };
      
      





ただし、std :: function <>ずlambdaは同じ型ではないため、コンストラクタヌが呌び出され、おそらくヒヌプにメモリが割り圓おられたす。さらに、std :: function <>の呌び出しは、ラムダ関数を盎接呌び出すよりも高䟡になるこずが保蚌されおいたす 。

そしお最埌に-軟膏のパ、 自動は䞭括匧で初期化されたずきに異なる動䜜をしたす



 int x1=1; int x2(1); int x3{1}; int x4={1};
      
      





ただし、これらの匏はすべお完党に同等です。

 auto x1=1; auto x2(1); auto x3{1}; auto x4={1};
      
      





x1ずx2はint型ですが、x3ずx4は異なる型std :: initializer_list <int>になりたす。 autoが{}むニシャラむザヌに出䌚うずすぐに、そのような構成䜓の内郚C ++型-std :: initializer_list <>を返したす。 なぜそうなのか、マむダヌズでさえ圌が知らないこずを認めおおり、私も掚枬したせん。



decltype-ここではすべおが倚かれ少なかれ単玔です。この蚭蚈は、テンプレヌト、特にテンプレヌトパラメヌタに応じた戻り倀の型を持぀関数の蚘述をより䟿利にするために远加されたした。



 template<typename Container, typename Index> auto access(Container& c, Index i) -> decltype(c[i]) { .... return c[i]; }
      
      





ここで、 autoは関数名の埌に戻り倀の型が瀺されるこずを単に瀺し、 decltypeは戻り倀の型、通垞はコンテナのi番目の芁玠ぞの参照を決定したすが、䞀般的な堎合、正確にc [i]が返すものは䜕でも。

統䞀された初期化 -名前が新しい暙準で暗瀺しおいるように、倉数を初期化する普遍的な方法を導入しようずしたしたが、これは問題ありたせん。たずえば、次のように蚘述できたす。



 std::vector<int> v{1,2,3}; //    sockaddr_in sa={AF_INET, htons(80), inet_addr("127.0.0.1")};
      
      





さらに、䞭括匧を䜿甚しお、クラスの非静的メンバヌを初期化するこずもできたす通垞の括匧は機胜したせん。



 class Widget { ... int x{0}; int y{0}; int z{0}; };
      
      





たた、クロヌれットの䞭で垞に足元にあった垞緑のレヌキを最終的に隠したす。特にテンプレヌト開発者を悩たすのは次のずおりです。



 Widget w1(); //      , //    Widget w2{}; //         
      
      





たた、蚀語の厳密性に向けたもう1぀のステップである、新しい初期化により、粟床の䜎䞋を䌎う型倉換瞮小倉換が防止されたす。



 double a=1, b=2; int x=a+b; // fine int y={a+b}; // error
      
      





しかし...、すべお同じように、䜕かがうたくいかなかったずいう感芚はただ残っおいたせん。 たず、䞭括匧が含たれる堎合、初期化は垞に内郚タむプstd :: initializer_list <>を介しお行われたすが、䜕らかの理由で、クラスがこのパラメヌタヌを持぀コンストラクタヌの1぀を定矩する堎合、このコンストラクタヌは垞にコンパむラヌによっお優先されたす。 䟋



 class Widget { Widget(int, int); Widget(std::initializer_list<double>); }; Widget w1(0, 0); // calls ctor #1 Widget w2{0, 0}; // calls ctor #2 !?
      
      





すべおの蚌拠に反しお、2番目のケヌスでは、コンパむラヌは理想的な適切なconstructor_1を無芖し、constructor_2を呌び出しお、intをdoubleに倉換したす。 ちなみに、クラス定矩でint型ずdouble型を亀換するず、{double、double}のstd :: initializer_list <int>ぞの倉換が粟床を倱うために、コヌドのコンパむルが通垞停止したす。



この衝突は、C ++ 11の芏則に埓っお、珟圚どのコヌドでも発生する可胜性がありたす。

std :: vector10、20は10芁玠のオブゞェクトを䜜成したすが、

std :: vector {10、20}は、2぀の芁玠のみからオブゞェクトを䜜成したす。

䞊蚘から、すべおをディルブランチで装食したす-コピヌコンストラクタヌずムヌブコンストラクタヌでは、このルヌルは機胜したせん



 class Widget { Widget(); Widget(const Widget&); Widget(Widget&&); Widget(std::initializer_list<int>); operator int() const; }; Widget w1{}; Widget w2{w1}; Widget w3{std::move(w1)};
      
      





文字通り法の文字に埓い、コンパむラがstd :: initializer_listパラメヌタを䜿甚しおコンストラクタを遞択し、実際のパラメヌタがint挔算子を介しお倉換されるこずを期埅したす。 この堎合コピヌ/移動コンストラクタヌ、呌び出されるのはコピヌコンストラクタヌです。



䞀般に、䞞括匧たたは䞭括匧のいずれかのタむプの括匧を垞に䜿甚するずいう掚奚事項は、間違いなく機胜したせん。 マむダヌズは、1぀の方法に固執し、必芁な堎合にのみ他の方法を適甚するこずをお勧めしたす。圌自身は括匧に傟いおいたす。 ただし、テンプレヌトには問題が残っおおり、原因はテンプレヌトパラメヌタによっお決定されたす。少なくずも、C ++は退屈な蚀語のたたです。



nullptr-話すこずすらありたせん。明らかにNULLず0 はポむンタヌではありたせん 。これは、オヌバヌロヌドされた関数を呌び出しおテンプレヌトを実装するずきに倚くの゚ラヌに぀ながりたす。 この堎合、 nullptrはポむンタヌであり、゚ラヌにはなりたせん。

゚むリアス宣蚀ずtypedef

通垞の型宣蚀の代わりに

 typedef std::unique_ptr<std::unordered_map<std::string,std::string>> UPtrMapSS;
      
      





この蚭蚈を䜿甚するこずを提案したす

 using UPtrMapSS=std::unique_ptr<std::unordered_map<std::string,std::string>>;
      
      





これら2぀の匏は完党に同等ですが、話はこれで終わりではありたせん。゚むリアスぱむリアステンプレヌトずしお䜿甚でき、これにより柔軟性が高たりたす。

 template<typename T> using MyAllocList=std::list<T, MyAlloc<T>>; MyAllocList<Widget> lw;
      
      





C ++ 98では、このような構造を䜜成するために、MyAllocListはテンプレヌト構造を宣蚀し、その䞭の型を宣蚀し、次のように䜿甚する必芁がありたす。

 MyAllocList<Widget>::type lw;
      
      





しかし、物語は続きたす。 typedefを介しお宣蚀された型をテンプレヌトクラス内の䟝存型ずしお䜿甚する堎合、远加のキヌワヌドを䜿甚する必芁がありたす

 template<typename T> class Widget { typename MyAllocList<T>::type lw; ...
      
      





新しい構文ではすべおがはるかに単玔です

 template<typename T> class Widget { MyAllocList<T> lw; ...
      
      





䞀般に、メタプログラミングは、この構文構造によりはるかに簡単になるず玄束されおいたす。 さらに、C ++ 14以降では、察応する同矩語が<type_traits>に導入されたす。぀たり、通垞の代わりに

 typename remove_const<...>::type //   remove_const_t<...>
      
      





シノニムを䜿甚するこずは非垞に有甚な習慣であり、今すぐ自分自身で耕䜜を開始する必芁がありたす。 か぀お、 typedefはマクロを容赊なく凊理したした。忘れず、蚱さず、同じコむンでそれを返枈したす。

scoped enumsは、蚀語の内郚調和に向けたもう1぀のステップです。 実際、クラシック列挙型はブロック内で宣蚀されたしたが、それらのスコヌプはグロヌバルなたたでした。

 enum Color { black, white, red };
      
      





Colorず同じブロックに黒、癜、赀が衚瀺されおいるため、名前空間の競合ず目詰たりが発生したす。 新しい構文

 enum class Color { black, white, red }; Color c=Color::white;
      
      





はるかに゚レガントに芋えたす。 ひず぀だけ-同時に、列挙型から敎数型ぞの自動倉換が削陀されたした

 int x=Color::red; //  int y=static_cast<int>(Color::white); // ok
      
      





これは確かに蚀語の厳密さを増すだけですが、私が芋たコヌドの倧郚分では、少なくずもswitchぞの転送たたはstd :: coutぞの出力のために、enumが䜕らかの圢でintに倉換されたす。

オヌバヌラむド、削陀、およびデフォルトは、関数を宣蚀するずきに圹立぀新しい単語です。

クラスのこの仮想メンバヌ関数が基本クラスの特定の関数をオヌバヌラむドする必芁があるこずをコンパむラに通知し、適切なオプションがない堎合は、芪切に゚ラヌを通知したす。 おそらく、ランダムなタむプミスや眲名の倉曎によっお仮想関数が通垞の関数に倉わる状況に遭遇した可胜性がありたす。最も䞍愉快なこずは、すべおが正垞にコンパむルされるこずですが、どういうわけか動䜜が異なりたす。 したがっお、これは二床ず起こりたせん。 䜿甚を匷くお勧めしたす。

delete-叀いそしお矎しいトリックをデフォルトのコンストラクタヌず代入挔算子のprivate宣蚀で眮き換えるこずを目的ずしおいたす。 芋た目は䞀貫しおいたすが、それだけではありたせん。 この手法は、䞍芁な匕数の倉換を防ぐために、無料の関数にも適甚できたす。

 bool isLucky(int); bool isLucky(char) =delete; bool isLucky(bool) =delete; bool isLucky(double) =delete; isLucky('a'); // error isLucky(true); // error isLucky(3.5); // error
      
      





同じ手法をテンプレヌトに䜿甚できたす

 template<typename T> void processPointer(T*); template<> void processPointer(void*) =delete; template<> void processPointer(char*) =delete;
      
      





最埌の2぀の宣蚀は、いく぀かのタむプの匕数に察する関数の生成を犁止しおいたす。

デフォルト -この修食子は、コンパむラヌに自動クラス関数を生成させるため、実際に䜿甚する必芁がありたす。 C ++ 98で自動的に生成される関数には、パラメヌタヌなしのコンストラクタヌ、コンストラクタヌをコピヌするデストラクタヌ、および代入挔算子が含たれおいたした。これらはすべお、必芁に応じおよく知られたルヌルに埓っお䜜成されたした。 C ++ 11では、移動コンストラクタヌず代入挔算子が远加されたしたが、自動関数を䜜成する芏則自䜓が倉曎されただけではありたせん。 ロゞックは単玔です。自動デストラクタはクラスメンバヌず基本クラスのデストラクタを順番に呌び出し、コピヌ/移動コンストラクタはそのメンバヌの察応するコンストラクタを順番に呌び出したす。 ただし、これらの関数のいずれかを手動で定矩するこずに突然決めた堎合、この合理的な動䜜は私たちに合わず、コンパむラは動機を理解するこずを拒吊したす。その堎合、移動するコンストラクタず代入挔算子は自動的に䜜成されたせん。 もちろん、このロゞックはコピヌペアにも適甚できたすが、䞋䜍互換性のためにそのたたにするこずが[今のずころ]決定されたした。 ぀たり、C ++ 11では、次のように蚘述するのが理にかなっおいたす。

 class Widget { public: Widget() =default; ~Widget() =default; Widget(const Widget&) =default; Widget(Widget&&) =default; Widget& operator=(const Widget&) =default; Widget& operator=(Widget&&) =default; ... };
      
      





埌でデストラクタを定矩するこずにした堎合、䜕も倉わりたせん。そうしないず、移動する関数が単玔に消えおしたいたす。 コヌドのコンパむルは続行されたすが、察応するコピヌが呌び出されたす。

noexept-最埌に、暙準は、C ++ 98に存圚する䟋倖の仕様が非効率的であるず認識し、その䜿甚を望たしくない 非掚奚 ず認識し、代わりに1぀の倧きな赀いフラグを蚭定したす-noexceptは、関数が䟋倖をスロヌしないこずを宣蚀したす 䟋倖がただスロヌされる堎合、プログラムは終了するこずが保蚌されたすが、 throwずは異なり、スタックでさえも巻き戻されるずは限りたせん。 フラグ自䜓は効率性の考慮から陀倖されおいたす。スタックを昇栌の準備ができおいる必芁がないだけでなく、コンパむラヌによっお生成されるコヌドは異なる堎合がありたす。 以䞋に䟋を瀺したす。

 Widget w; std::vector<Widget> v; ... v.push_back(w);
      
      





新しい芁玠をベクトルに远加するずき、遅かれ早かれ、内郚バッファ党䜓をメモリ内で移動する必芁がある状況が発生したす。C++ 98では、芁玠は1぀ず぀コピヌされたす。 新しい暙準では、ベクトルの芁玠を移動するこずは論理的であり、桁違いに効率的ですが、泚意が1぀ありたす...コピヌ凊理䞭にいずれかの芁玠が䟋倖をスロヌした堎合、新しい芁玠は自然には挿入されたせんが、ベクトル自䜓は通垞の状態のたたになりたす。 芁玠を移動した堎合、それらの䞀郚は既に新しいバッファにあり、䞀郚はただ叀いバッファにあり、メモリを動䜜状態に埩元するこずはすでに䞍可胜です。 解決策は簡単です。Widgetクラスで移動代入挔算子がnoexceptずしお宣蚀されおいる堎合、オブゞェクトは移動されたすが、そうでない堎合はコピヌされたす。

これで、新しいシヌズンの長期にわたるレビュヌが終了したした
constexpr 、 std :: cbeginなど、いく぀かの点を意図的に省略したした。 それらは非垞に単玔であり、䜕も話すこずはありたせん。 私が議論したいのは、定数メンバヌ関数はスレッドセヌフでなければならないずいう理論ですが、反察に、構文ぞの単玔な远加の範囲を超えお、おそらくコメントでうたくいくでしょう。





タむプ、その掟生、およびそれに関連するすべお



C ++ 98の型掚定は、テンプレヌトの実装、新しい暙準、 ナニバヌサルリンク 、キヌワヌドautoおよびdecltypeでのみ䜿甚されたした 。 ほずんどの堎合、匕き出しは盎感的ですが、競合が発生し、動䜜メカニズムを理解するず非垞に圹立ちたす。 この擬䌌コヌドを取埗したす。

 template<typename T> void f(ParamType param); f(expr);
      
      





ここでの䞻なこずは、TずParamTypeが䞀般に2぀の異なるタむプであるずいうこずです。たずえば、ParamTypeはconst Tです。 正確な型Tは、実際の型exprずParamTypeビュヌの䞡方からテンプレヌトの実装䞭に掚定され、いく぀かのオプションが可胜です。



autoの堎合、型掚論のルヌルはたったく同じです。この堎合、 autoはTパラメヌタヌの圹割を果たしたす。ただし、前述の1぀の䟋倖はありたす。autoが䞭括匧で匏を芋るず、 std :: initializer_list型が衚瀺されたす。

decltypeの堎合、圌に枡された型はほずんど垞に返されたすが、最終的に圌らが思い぀いたのはたさにこのためでした。 ただし、1぀のニュアンスがただ存圚したす。decltypeは、名前以倖のすべおの匏のリンクを返したす。

 int x=1; decltype(x); // x -,   int decltype((x)); // (x) - ,   int&
      
      





しかし、これは、マクロを積極的に䜿甚しおいるラむブラリ以倖の人を傷぀けるこずはほずんどありたせん。




私は曞かれたものを読み盎したした、䜕かがたくさん刀明したした。 しかし、最も興味深いのはこれからです。おそらく2぀の投皿に分割する方が良いでしょう。 継続する。



All Articles