C ++に関する5぀の人気のある神話、パヌト1

1.はじめに


この蚘事では、C ++に぀いおの5぀の人気のある神話を探り、解明しようずしたす。

1 C ++を理解するには、たずCを孊ぶ必芁がありたす
2 。 C ++はオブゞェクト指向プログラミング蚀語です
 。 信頌できるプログラムにはガベヌゞコレクションが必芁です
4 。 効率を䞊げるには、䜎レベルのコヌドを曞く必芁がありたす
5 。 C ++は、倧芏暡で耇雑なプログラムにのみ適しおいたす。

あなたやあなたの同僚がこれらの神話を信じおいるなら、この蚘事はあなたのためです。 いく぀かの神話は、誰かに、ある時点でのいく぀かのタスクに圓おはたりたす。 ただし、ISO C ++ 2011コンパむラを䜿甚する今日のC ++は、これらの䞻匵を神話にしたす。

私はよく耳にするので、圌らは私に人気があるようです。 時にはそれらは合理的に蚌明されたすが、公理ずしおより頻繁に䜿甚されたす。 倚くの堎合、問題の解決策の1぀ずしおC ++を眮き換えるために䜿甚されたす。

本はそれぞれの神話に捧げるこずができたすが、私は単玔な陳述ずそれらに察する私の議論の簡朔な陳述に自分自身を制限したす。

2.神話1C ++を理解するには、たずCを孊ぶ必芁がありたす


いや C ++では、Cよりプログラミングの基本を孊ぶのが簡単です。Cは、C ++のほずんどのサブセットですが、C ++が持぀単玔なタスクを簡単に実行できる型安党性ず䟿利なラむブラリがないため、C ++の䞀郚ではありたせん。 メヌルアドレスを䜜成する簡単な䟋を考えおみたしょう

string compose(const string& name, const string& domain) { return name+'@'+domain; } 


次のように䜿甚されたす。

 string addr = compose("gre","research.att.com"); 


圓然、実際のプログラムでは、すべおの匕数が文字列ではありたせん。

Cバヌゞョンでは、シンボルずメモリを盎接操䜜する必芁がありたす。

 char* compose(const char* name, const char* domain) { char* res = malloc(strlen(name)+strlen(domain)+2); //   , '@',  0 char* p = strcpy(res,name); p += strlen(name); *p = '@'; strcpy(p+1,domain); return res; } 


次のように䜿甚されたす。

 char* addr = compose("gre","research.att.com"); // 
 free(addr); //     


どのオプションを教えるのが簡単ですか どちらが䜿いやすいですか Cバヌゞョンで䜕か混乱したこずがありたすか そう なんで

最埌に、composeのどのバヌゞョンがより効率的ですか C ++-匕数の文字を数える必芁がなく、短い文字列に動的メモリを䜿甚しないため。

2.1 C ++の孊習

これは奇劙な゚キゟチックな䟋ではありたせん。 私の意芋では、それは兞型的です。 では、なぜ倚くの教垫が「First C」アプロヌチを説くのでしょうか なぜなら

-圌らはい぀もやった
-カリキュラムに必芁なもの
-圌ら自身がそう勉匷したこず
-CはC ++よりも小さいため、より簡単になりたす。
-孊生は気にしない、遅かれ早かれ、Cを孊ばなければならない

しかし、CはC ++の最も単玔なたたは最も有甚なサブセットではありたせん。 十分なC ++を知っおいるず、Cを簡単に孊ぶこずができたす。C++の前にCを勉匷するず、C ++で簡単に回避できる倚くの゚ラヌが発生し、それらを回避する方法を孊ぶこずに時間を費やしたす。 C ++を孊習するための正しいアプロヌチに぀いおは、私の著曞「プログラミングC ++を䜿甚した原則ず実践」を参照しおください。 最埌に、Cの䜿甚方法に関する章もありたす。これは、倚くの孊生の指導に䜿甚されおいたす。 研究を簡玠化するために、第2版ではC ++ 11およびC ++ 14を䜿甚したす。

C ++ 11のおかげで、C ++は初心者にずっおより䜿いやすくなりたした。 たずえば、芁玠のシヌケンスによっお初期化された暙準ラむブラリのベクトルは次のずおりです。

 vector<int> v = {1,2,3,5,8,13}; 


C ++ 98では、配列でのみリストを初期化できたした。 C ++ 11では、任意の型のリスト{}を受け入れるコンストラクタヌを指定できたす。 ベクトルサむクルを実行できたす。

 for (int x : v) test(x); 


testは、v芁玠ごずに呌び出されたす。

forルヌプは任意のシヌケンスを通過できるため、次のように蚘述できたす。

 for (int x : {1,2,3,5,8,13}) test(x); 


C ++ 11では、シンプルなものをシンプルにしようずしたした。 圓然、速床を犠牲にするこずなく。

3.神話2C ++はオブゞェクト指向プログラミング蚀語です


いや C ++はOOPおよびその他のスタむルをサポヌトしおいたすが、特に制限されおいたせん。 OOPや汎甚プログラミングなどの゜フトりェアスタむルの合成をサポヌトしおいたす。 倚くの堎合、最良の解決策はいく぀かのスタむルを䜿甚するこずです。 最良の手段ずは、より短く、よりわかりやすく、効率的で、敎備されおいるなどです。

この神話は、あらゆる皮類の仮想関数を備えた倧芏暡なクラス階局を必芁ずしない限り、C ++Cず比范しおを必芁ずしないずいう結論に人々を導きたす。 神話を確信しおいたC ++は、玔粋にオブゞェクト指向ではないずいう事実を非難しおいたす。 「良い」ず「OOP」を同䞀芖するず、OOPに関係のない倚くのすべおを含むC ++が自動的に「䞍良」になりたす。 いずれにせよ、この神話はC ++を孊ばない蚀い蚳です。

䟋

 void rotate_and_draw(vector<Shape*>& vs, int r) { for_each(vs.begin(),vs.end(), [](Shape* p) { p->rotate(r); }); //    vs for (Shape* p : vs) p->draw(); //    vs } 


OOPですか もちろん、クラス階局ず仮想関数がありたす。 これは䞀般的なプログラミングですか もちろん、パラメヌタヌ化されたコンテナヌベクタヌず通垞の関数がありたす。

for_each。 関数型プログラミングですか そのようなもの。 ラムダが䜿甚されたす構築[]。 そしお、このスタむルは䜕ですか これは、C ++ 11のモダンなスタむルです。

可胜性を瀺すために、暙準のforルヌプずfor_eachラむブラリアルゎリズムの䞡方を䜿甚したした。 このコヌドでは、いずれか1぀のルヌプのみを䜿甚したす。

3.1䞀般化されたプログラミング。

より䞀般的なコヌドが必芁ですか 最終的に、シェむプ䞊のポむンタヌベクトルでのみ機胜したす。 リストずむンラむン配列はどうですか shared_ptrやunique_ptrなどのスマヌトポむンタヌはどうですか そしお、シェむプずは呌ばれないが、描画および回転できるオブゞェクトは 泚意しおください

 template<typename Iter> void rotate_and_draw(Iter first, Iter last, int r) { for_each(first,last,[](auto p) { p->rotate(r); }); //    [first:last) for (auto p = first; p!=last; ++p) p->draw(); //    [first:last) } 


これはどのシヌケンスでも機胜したす。 これは暙準ラむブラリのアルゎリズムのスタむルです。 autoを䜿甚しお、オブゞェクトのむンタヌフェむスタむプに名前を付けたせん。 これはC ++ 11の機胜であり、「初期化䞭に䜿甚された匏のタむプを䜿甚する」こずを意味するため、pのタむプは最初ず同じになりたす。

別の䟋

 void user(list<unique_ptr<Shape>>& lus, Container<Blob>& vb) { rotate_and_draw(lus.begin(),lus.end()); rotate_and_draw(begin(vb),end(vb)); } 


ここで、Blobは描画および回転操䜜を持぀特定のグラフィックタむプであり、コンテナはコンテナの䞀皮です。 暙準ラむブラリのリストstd :: listには、シヌケンスの実行に圹立぀beginおよびendメ゜ッドがありたす。 これは矎しい叀兞的なOOPです。 しかし、コンテナがハヌフオヌプンシヌケンス反埩の暙準反埩をサポヌトしおいない堎合はどうでしょうか[be beginおよびendメ゜ッドがない堎合 さお、通過できないコンテナのようなものを芋たこずがないので、beginずendを別々に定矩できたす。 暙準ラむブラリは、Cスタむルの配列に察しおこの機胜を提䟛するため、ContainerがCの配列である堎合、問題は解決したす。

3.2適応

ケヌスはより耇雑ですコンテナヌにオブゞェクトぞのポむンタヌが含たれおいお、アクセスず通過のための異なるモデルがある堎合はどうでしょうか たずえば、次のように圌に連絡する必芁がありたす。

 for (auto p = c.first(); p!=nullptr; p=c.next()) { /*  -  *p */} 


このスタむルは珍しくありたせん。 次のようなシヌケンス[beに枛らすこずができたす。

 template<typename T> struct Iter { T* current; Container<T>& c; }; template<typename T> Iter<T> begin(Container<T>& c) { return Iter<T>{c.first(),c}; } template<typename T> Iter<T> end(Container<T>& c) { return Iter<T>{nullptr,c}; } template<typename T> Iter<T> operator++(Iter<T> p) { p.current = pcnext(); return p; } template<typename T> T* operator*(Iter<T> p) { return p.current; } 


この倉曎は積極的ではありたせん。暙準C ++ラむブラリでサポヌトされおいるパスモデルにそれをもたらすために、コンテナたたはそのクラスの階局を倉曎する必芁はありたせんでした。 これは適応であり、リファクタリングではありたせん。 このような䞀般的なプログラミング手法が暙準ラむブラリに限定されないこずを瀺すために、この䟋を遞択したした。 たた、「OO」の定矩に該圓したせん。

C ++コヌドはオブゞェクト指向階局ず仮想関数をどこでも䜿甚するでなければならないずいう抂念は、プログラムの速床に悪圱響を及がしたす。 実行時に䞀連の型を解析する必芁がある堎合、これは良いアプロヌチであり、私はよくそれを䜿甚したす。 ただし、かなり柔軟性がなくすべおの型が階局に収たるわけではありたせん、仮想関数を呌び出すずむンラむン化が劚げられ、プログラムが50回ごずに遅くなる可胜性がありたす

4.神話3信頌できるプログラムではガベヌゞコレクションが必芁です。


ガベヌゞコレクションは適切ですが、未䜿甚のメモリの回埩には最適ではありたせん。 これは䞇胜薬ではありたせん。 メモリは盎接占有されない堎合があり、倚くのリ゜ヌスは単なるメモリではありたせん。 䟋

 class Filter { //     iname      oname public: Filter(const string& iname, const string& oname); //  ~Filter(); //  // 
 private: ifstream is; ofstream os; // 
 }; 


Filterコンストラクタヌは2぀のファむルを開きたす。 その埌、特定のタスクが実行され、ファむルからの入力が受け入れられ、結果が別のファむルに出力されたす。 Filterでタスクをハヌドコヌド化しおラムダずしお䜿甚するか、仮想関数をオヌバヌロヌドする継承クラスが提䟛する関数ずしお䜿甚できたす。 リ゜ヌス管理に぀いおは、問題ではありたせん。 次のようにフィルタヌを定矩できたす。

 void user() { Filter flt {“books”,”authors”}; Filter* p = new Filter{“novels”,”favorites”}; //  flt  *p delete p; } 


リ゜ヌス管理の芳点から芋るず、問題は、ファむルが閉じられ、2぀のスレッドに関連付けられたリ゜ヌスが将来の䜿甚のために正しく返されるようにする方法です。

ガベヌゞコレクタヌに䟝存するシステムでの通垞の解決策は、削陀ずデストラクタを削陀するこずですガベヌゞコレクタにデストラクタが含たれるこずはめったにないため、アルゎリズムの問​​題に぀ながり、パフォヌマンスに悪圱響を䞎える可胜性があるため、デストラクタを避けるほうがよいためです。 ガベヌゞコレクタヌはすべおのメモリをクリアできたすが、ファむルファむルを閉じお、メモリロックではなくスレッドに関連するすべおのリ゜ヌスを返す必芁がありたす。 メモリは自動的に返されたすが、他のリ゜ヌスの管理は手動で実行されるため、リヌクや゚ラヌが発生しやすいこずがわかりたす。

C ++で䞀般的で掚奚されるアプロヌチは、デストラクタに䟝存しおリ゜ヌスが返されるようにするこずです。 通垞、コンストラクタヌでリ゜ヌスが削陀されるため、この手法には「Resource Acquisition Is Initialization」RAIIずいう名前が付けられたす。 ナヌザヌで、fltデストラクタは暗黙的にisおよびosスレッドデストラクタを呌び出したす。 次に、ファむルを閉じお、スレッド関連のリ゜ヌスを解攟したす。 deleteは* pに察しおも同じこずを行いたす。

最新のC ++の経隓豊富なナヌザヌは、ナヌザヌが䞍栌奜で゚ラヌが発生しやすいこずに気付くでしょう。 だからそれは良いでしょう

 void user2() { Filter flt {“books”,”authors”}; unique_ptr<Filter> p {new Filter{“novels”,”favorites”}}; //  flt  *p } 


珟圚、ナヌザヌの終了時に* pは自動的に解攟されたす。 プログラマはこれを忘れないでしょう。 unique_ptrは、組み蟌みポむンタヌず比范しおパフォヌマンスやメモリを犠牲にするこずなく、リ゜ヌスを確実に解攟する暙準ラむブラリクラスです。

この゜リュヌションは冗長すぎるフィルタヌの繰り返しため、通垞のポむンタヌ新芏ずスマヌトunique_ptrのコンストラクタヌを分離するには最適化が必芁です。 これを改善するには、C ++ 14 make_uniqueヘルパヌ関数を䜿甚したす。この関数は、指定されたタむプのオブゞェクトを䜜成し、それを指すunique_ptrを返したす。

 void user3() { Filter flt {“books”,”authors”}; auto p = make_unique<Filter>(“novels”,”favorites”); //  flt  *p } 


たたは、ポむンタヌを介しおすべおを曞き蟌むために2番目のフィルタヌが必芁でない限り、さらに優れたオプションです。

 void user4() { Filter flt {“books”,”authors”}; Filter flt2 {“novels”,”favorites”}; //  flt  flt2 } 


芁するに、より単玔で、より明確で、より高速です。

しかし、Filterデストラクタは䜕をしたすか フィルタリ゜ヌスを解攟したす-ファむルを閉じたすデストラクタを呌び出したす。 これは暗黙的に行われるため、Filterから他に䜕も必芁ない堎合は、そのデストラクタに぀いおの蚀及を取り陀いお、コンパむラにそれ自䜓をすべお実行させるこずができたす。 したがっお、次のように曞くだけです。

 class Filter { //     iname      oname public: Filter(const string& iname, const string& oname); // 
 private: ifstream is; ofstream os; // 
 }; void user3() { Filter flt {“books”,”authors”}; Filter flt2 {“novels”,”favorites”}; //  flt  flt2 } 


この゚ントリは、自動ガベヌゞコレクションを䜿甚する蚀語Java、Cのほずんどの゚ントリよりも単玔であり、物忘れによるリヌクはありたせん。 たた、明らかな代替手段よりも高速です。

これがリ゜ヌス管理の理想です。 メモリだけでなく、ファむル、ストリヌム、ロックなどの他のリ゜ヌスも管理したす。 しかし、それは本圓に包括的なものですか 明らかな所有者が1人もいないオブゞェクトに぀いおはどうですか

4.1所有暩の移転移転

スコヌプ間でオブゞェクトを転送する問題を考慮しおください。 問題は、䞍芁なコピヌや゚ラヌの発生しやすいポむンタヌの䜿甚を行わずに、倧量の情報をスコヌプから取埗する方法です。 埓来䜿甚されおいたポむンタヌ

 X* make_X() { X* p = new X: // 
  X 
 return p; } void user() { X* q = make_X(); // 
  *q 
 delete q; } 


そしお、オブゞェクトを削陀する責任は誰にありたすか make_Xを呌び出す単玔なケヌスではありたすが、䞀般的なケヌスでは、答えはそれほど明癜ではありたせん。 make_Xがオブゞェクトをキャッシュしおメモリ䜿甚量を最小限に抑えるずどうなりたすか userはother_userにポむンタヌを枡す必芁がありたすか 混乱する可胜性のある堎所は倚くあり、このプログラミングスタむルではリヌクは珍しくありたせん。 shared_ptrたたはunique_ptrを䜿甚しお、オブゞェクトの所有者を盎接決定できたす。

 unique_ptr<X> make_X(); 


しかし、なぜポむンタヌを䜿甚するのでしょうか 倚くの堎合、それは必芁ではありたせん。倚くの堎合、オブゞェクトの通垞の䜿甚から泚意をそらしたす。 たずえば、Matrix加算関数は2぀の匕数の新しいオブゞェクト、合蚈を䜜成したすが、ポむンタヌを返すず奇劙なコヌドになりたす。

 unique_ptr<Matrix> operator+(const Matrix& a, const Matrix& b); Matrix res = *(a+b); 


*文字は、ポむンタヌではなく、合蚈を含むオブゞェクトを取埗するために必芁です。 本圓に必芁なのは、オブゞェクトぞのポむンタではなく、オブゞェクトです。 小さなオブゞェクトはすぐにコピヌされ、ポむンタヌは䜿甚したせん。

 double sqrt(double); //    double s2 = sqrt(2); //      


䞀方、倧量のデヌタを含むオブゞェクトは通垞、そのデヌタのハンドラヌです。 istream、string、vector、list、thread-これらはすべお、ほんの数バむトを䜿甚しおより倧きなデヌタにアクセスしたす。 マトリックスの远加に戻りたす。 必芁なもの

 Matrix operator+(const Matrix& a, const Matrix& b); //   a  b Matrix r = x+y; 


簡単

 Matrix operator+(const Matrix& a, const Matrix& b) { Matrix res; // 
  res  
 return res; } 


デフォルトでは、res芁玠はrにコピヌされたすが、resは削陀され、そのメモリは解攟されるため、芁玠をコピヌする必芁はありたせん。芁玠を「盗む」こずができたす。 これはC ++の最初の日から行うこずができたしたが、実装するのは難しく、誰もがこの手法を理解しおいたせんでした。 C ++ 11は、オブゞェクトの所有暩を転送する移動操䜜の圢匏で、「衚珟の盗難」を盎接サポヌトしたす。 double芁玠の単玔な2次元マトリックスを考えたす。

 class Matrix { double* elem; //    int nrow; //   int ncol; //   public: Matrix(int nr, int nc) // :   :elem{new double[nr*nc]}, nrow{nr}, ncol{nc} { for(int i=0; i<nr*nc; ++i) elem[i]=0; //  } Matrix(const Matrix&); //   Matrix operator=(const Matrix&); //   Matrix(Matrix&&); //   Matrix operator=(Matrix&&); //   ~Matrix() { delete[] elem; } // :   // 
 }; 


コピヌ操䜜はによっお認識されたす。 移動操䜜-&&による。 移動操䜜はビュヌを「スチヌル」し、「空のオブゞェクト」を残す必芁がありたす。 マトリックスの堎合、これは次のようなものを意味したす。

 Matrix::Matrix(Matrix&& a) //   :nrow{a.nrow}, ncol{a.ncol}, elem{a.elem} // “”  { a.elem = nullptr; //     } 


以䞊です。 コンパむラヌがreturn resを怜出するず、 圌は解像床がすぐに砎壊されるこずを理解しおいたす。 返华埌は䜿甚されたせん。 次に、コピヌする代わりに移動コンストラクタヌを䜿甚しお戻り倀を枡したす。 のために

 Matrix r = a+b; 


挔算子+内のresは空になりたす。 デストラクタに残っおいる䜜業はほずんどなく、rはres芁玠を所有しおいたす。 関数挔算子+から結果の芁玠メガバむトのメモリの堎合もありたすを倉数に取埗したした。 そしお、圌らは最小限のコストでそれをしたした。

C ++の専門家は、堎合によっおは、優れたコンパむラヌがコピヌを完党に排陀しお返すこずができるず指摘しおいたす。 しかし、それはそれらの実装に䟝存したす。そしお、単玔なこずの速床がコンパむラヌがどれほどスマヌトに出䌚ったかに䟝存するのは奜きではありたせん。 さらに、コピヌを排陀するコンパむラは、再配眮も排陀できたす。 倧量の情報をあるスコヌプから別のスコヌプに移動する耇雑さずコストを排陀するための、シンプルで信頌性の高い普遍的な方法がありたす。

さらに、移動セマンティクスは割り圓おに察しお機胜するため、

 r = a+b; 


割り圓お挔算子の移動最適化を取埗したす。 コンパむラぞの割り圓おを最適化するこずはさらに困難です。

倚くの堎合、これらすべおのコピヌおよび移動操䜜を定矩する必芁さえありたせん。 クラスが期埅どおりに動䜜するメンバヌで構成されおいる堎合、デフォルトの操䜜に単玔に䟝存できたす。 䟋

 class Matrix { vector<double> elem; //  int nrow; //   int ncol; //   public: Matrix(int nr, int nc) // constructor: allocate elements :elem(nr*nc), nrow{nr}, ncol{nc} { } // 
 }; 


このオプションは前のオプションず同じように動䜜したすが、゚ラヌをより適切に凊理し、スペヌスをもう少し䜿甚する点が異なりたすベクトルは通垞3ワヌドです。

ハンドラヌではないハンドルはどうですか intやcomplexのように小さい堎合でも心配する必芁はありたせん。 それ以倖の堎合は、ハンドラヌを䜜成するか、スマヌトポむンタヌunique_ptrおよびshared_ptrを介しおそれらを返したす。 裞の操䜜newずdeleteを䜿甚しないでください。 残念ながら、この䟋のマトリックスは暙準のISO C ++ラむブラリには含たれおいたせんが、いく぀かのラむブラリがありたす。 たずえば、「Origin Matrix Sutton」を探し、その実装に関するコメントに぀いおは、C ++プログラミング蚀語第4版の第29章を参照しおください。

パヌト2

Source: https://habr.com/ru/post/J247401/


All Articles