C ++でのプログラミングの問題のある側面


C ++には、潜在的に危険と考えられる機能がかなりあります-設計の計算ミスや不正確なコーディングにより、簡単にエラーにつながる可能性があります。 この記事では、このような機能の選択を提供し、それらの悪影響を減らす方法に関するヒントを提供します。










目次



目次

はじめに

1.タイプ

1.1。 条件付き命令と演算子

1.2。 暗黙的な変換

2.名前解決

2.1。 ネストされたスコープ内の変数の非表示

2.2。 関数のオーバーロード

3.コンストラクタ、デストラクタ、初期化、削除

3.1。 コンパイラーによって生成されたクラスメンバー関数

3.2。 初期化されていない変数

3.3。 基本クラスと非静的クラスメンバーの初期化順序

3.4。 静的クラスメンバーとグローバル変数の初期化手順

3.5。 デストラクタの例外

3.6。 動的オブジェクトと配列の削除

3.7。 クラス宣言が不完全な場合の削除

4.演算子、式

4.1。 オペレーターの優先度

4.2。 オペレーターの過負荷

4.3。 部分式を計算する手順

5.仮想機能

5.1仮想関数のオーバーライド

5.2デフォルトパラメータのオーバーロードと使用

5.3コンストラクターとデストラクターでの仮想関数の呼び出し

5.4仮想デストラクタ

6.メモリを使用した直接作業

6.1オーバーランバッファー

6.2 Zで終わる文字列

6.3可変数のパラメーターを持つ関数

7.構文

7.1複雑な発表

7.2構文のあいまいさ

8.その他

8.1キーワードインラインおよびODR

8.2ヘッダーファイル

8.3 switchステートメント

8.4値によるパラメーターの受け渡し

8.5リソース管理

8.6所有リンクと非所有リンク

8.7バイナリ互換性

8.8マクロ

9.まとめ

参照資料









プレモニトゥス、プレエムニトゥス。

事前警告とは武装を意味します。 (lat。)









はじめに



C ++には、潜在的に危険と考えられる機能がかなりあります-設計の計算ミスや不正確なコーディングにより、簡単にエラーにつながる可能性があります。 それらのいくつかは、困難な子供時代に起因するもの、古いC ++ 98標準に起因するものもありますが、他のものは、最新のC ++の機能に既に関連付けられています。 主なものを検討し、それらのマイナスの影響を減らす方法についてアドバイスを与えてください。









1.タイプ





1.1。 条件付き命令と演算子



Cとの互換性の必要性は、 if(...)



などのステートメントで、 bool



ような式だけでなく、任意の数値式またはポインターを置き換えることができるという事実につながります。 この問題は、算術式のbool



からint



への暗黙的な変換と一部の演算子の優先度によって悪化します。 これにより、たとえば次のエラーが発生します。







if(a=b)



when if(a==b)





if(a<x<b)



when if(a<x && x<b)





if(a&x==0)



正しいif((a&x)==0)





if(Foo)



when when if(Foo())





if(arr)



正しくif(arr[0])



場合、

if(strcmp(s,r))



when when if(strcmp(s,r)==0)









これらのエラーの一部はコンパイラの警告を引き起こしますが、エラーは引き起こしません。 コードアナライザーも役立つ場合があります。 C#では、そのようなエラーはほとんど不可能ですbool



if(...)



などはbool



型を必要とするため、算術式にbool



型と数値型を混在させることはできません。







戦う方法:











1.2。 暗黙的な変換



C ++は厳密に型指定された言語を指しますが、コードを短くするために暗黙的な型変換が広く使用されています。 これらの暗黙的な変換は、場合によってはエラーにつながる可能性があります。







最も厄介な暗黙の変換は、数値型またはポインターのbool



への変換とbool



からint



への変換です。 セクション1.1で説明されている問題を引き起こすのは、これらの変換(Cとの互換性のために必要)です。 暗黙的な変換も常に適切であるとは限らず、たとえばdouble



からint



への数値データの精度の低下(変換の縮小)を引き起こす可能性があります。 多くの場合、コンパイラは警告を生成します(特に数値データの精度が低下する可能性がある場合)が、警告はエラーではありません。 C#では、数値型とbool



間の変換は(明示的であっても)禁止されており、数値データの精度が失われる可能性のある変換はほとんど常にエラーです。







プログラマは、他の暗黙的な変換を追加できます。(1) explicit



キーワードなしで1つのパラメーターでコンストラクターを定義することにより。 (2)型変換演算子の定義。 これらの変換は、強力なタイピングの原則に基づいて、追加のセキュリティギャップを突破しています。







C#では、組み込みの暗黙的な変換の数ははるかに少なく、 implicit



キーワードを使用してカスタムの暗黙的な変換を宣言する必要があります。







戦う方法:











2.名前解決





2.1。 ネストされたスコープ内の変数の非表示



C ++では、次の規則が適用されます。 させて







 //   {    int x;    // ... //  ,       {        int x;        // ...    } }
      
      





C ++ルールに従って、



宣言された変数



は、



宣言された変数



隠します



x



の最初の宣言はブロック内にある必要はありません。クラスまたはグローバル変数のメンバーにすることができ、ブロック



で表示される必要があります









次のコードをリファクタリングする必要がある状況を想像してください







 //   {    int x;    // ... //  ,       {    // -         } }
      
      





誤って、変更が行われます。







 //   {    //  , :    int x;    // -         // ...    //  :    // -      }
      
      





そして今、「何かが



から







で行われている」というコードは、



からの



何かをするでしょう! すべてが以前のように機能しないことは明らかであり、多くの場合非常に難しいものを見つけることです。 C#では、ローカル変数を隠すことは禁止されています(クラスメンバーはできますが)。 いずれかの形式で変数を非表示にするメカニズムは、ほとんどすべてのプログラミング言語で使用されていることに注意してください。







戦う方法:











2.2。 関数のオーバーロード



関数のオーバーロードは多くのプログラミング言語に不可欠な機能であり、C ++も例外ではありません。 ただし、この機会は慎重に使用する必要があります。そうしないと、問題が発生する可能性があります。 たとえば、設計者が過負荷になっている場合、プログラマには選択肢がない場合もありますが、過負荷の拒否を正当化できる場合もあります。 オーバーロードされた関数を使用するときに発生する問題を考慮してください。







オーバーロードを解決するときに発生する可能性のあるすべてのオプションを検討しようとすると、オーバーロードを解決するためのルールが非常に複雑になるため、予測が困難になります。 テンプレート関数と組み込み演算子のオーバーロードにより、追加の複雑さが導入されます。 C ++ 11は、右辺値リンクと初期化リストに問題を追加しました。







ネストされたスコープ内のオーバーロードを解決するための候補の検索アルゴリズムによって問題が発生する可能性があります。 コンパイラーが現在のスコープで候補を見つけた場合、さらなる検索は終了します。 見つかった候補が適切ではない、競合する、削除される、またはアクセスできない場合、エラーが生成されますが、それ以上の検索は試行されません。 そして、現在のスコープに候補がない場合のみ、検索は次のより広いスコープに移動します。 名前隠蔽メカニズムは機能します。これはセクション2.1で説明したものとほぼ同じです。[Dewhurst]を参照してください。







関数をオーバーロードすると、コードの可読性が低下する可能性があります。つまり、エラーが発生します。







既定のパラメーターを持つ関数を使用すると、オーバーロードされた関数を使用するように見えますが、もちろん潜在的な問題は少なくなります。 しかし、可読性が低く、エラーの可能性があるという問題は残っています。







細心の注意を払って、仮想関数のオーバーロードおよびデフォルトパラメータを使用する必要があります。セクション5.2を参照してください。







C#は関数のオーバーロードもサポートしていますが、オーバーロードを解決するためのルールは少し異なります。







戦う方法:











3.コンストラクタ、デストラクタ、初期化、削除





3.1。 コンパイラーによって生成されたクラスメンバー関数



プログラマーが次のリスト(デフォルトコンストラクター、コピーコンストラクター、コピー代入演算子、デストラクタ)からクラスのメンバー関数を定義していない場合は、コンパイラーがこれを行うことができます。 C ++ 11では、このリストに移動コンストラクターと移動割り当て演算子が追加されました。 これらのメンバー関数は、特別なメンバー関数と呼ばれます。 それらは使用される場合にのみ生成され、各機能に固有の追加の条件が満たされます。 この使用が完全に隠されている可能性があることに注意してください(たとえば、継承を実装する場合)。 必要な機能を生成できない場合、エラーが生成されます。 (再配置操作を除き、それらはコピー操作に置き換えられます。)コンパイラーによって生成されるメンバー関数は、パブリックで埋め込み可能です。 特別なメンバー関数の詳細は[Meyers2]にあります。







場合によっては、コンパイラからのそのようなヘルプは「ベアサービス」になります。 カスタムの特別なメンバー関数が存在しないと、些細な型が作成される可能性があり、これにより、初期化されていない変数の問題が発生します。セクション3.2を参照してください。 生成されたメンバー関数はパブリックであり、これは常にクラスの設計と一貫しているとは限りません。 基本クラスでは、コンストラクターを保護する必要があります;オブジェクトのライフサイクルをより細かく制御するには、保護されたデストラクタが必要になる場合があります。 クラスにメンバーとして生のリソース記述子があり、このリソースを所有している場合、プログラマはコピーコンストラクタ、コピー割り当て演算子、およびデストラクタを実装する必要があります。 いわゆる「ビッグ3のルール」はよく知られており、プログラマーが3つの操作(コピーコンストラクター、コピー割り当て演算子、またはデストラクター)の少なくとも1つを定義する場合、3つすべての操作を定義する必要があります。 コンパイラーが生成する移動コンストラクターと移動割り当て演算子も、常に必要なものとはほど遠いものです。 コンパイラによって生成されるデストラクタは、場合によっては非常に微妙な問題を引き起こし、リソースリークを引き起こす可能性があります。セクション3.7を参照してください。







プログラマーは、特別なメンバー関数の生成を禁止できます。C++ 11では、C ++ 98で、対応するメンバー関数をprivateとして宣言し、定義しない場合、 "=delete"



構文を使用する必要があります。







プログラマーがコンパイラーによって生成されたメンバー関数に慣れている場合、C ++ 11では、宣言をドロップするだけでなく、これを明示的に示すことができます。 これを行うには、宣言時に"=default"



構文を使用する必要がありますが、コードは読みやすく、アクセスレベルの制御に関連する追加機能が表示されます。







C#では、コンパイラーはデフォルトのコンストラクターを生成できます。通常、これは問題を引き起こしません。







戦う方法:











3.2。 初期化されていない変数



コンストラクタとデストラクタは、C ++オブジェクトモデルの重要な要素と呼ぶことができます。 オブジェクトを作成するとコンストラクタが呼び出され、削除するとデストラクタが呼び出されます。 しかし、Cとの互換性の問題により、いくつかの例外が強制され、この例外は単純型と呼ばれます。 これらは、コンストラクターとデストラクターの必須呼び出しなしで、変数のsichnyタイプとsyshnyライフサイクルをシミュレートするために導入されました。 C ++でコンパイルおよび実行される場合、CコードはCとまったく同じように動作します。通常の型には、数値型、ポインター、列挙、および単純な型で構成されるクラス、構造体、共用体、配列が含まれます。 クラスと構造は、カスタムコンストラクタ、デストラクタ、コピー、仮想関数が存在しないという追加条件を満たしている必要があります。 取るに足らないクラスの場合、コンパイラはデフォルトのコンストラクタとデストラクタを生成できます。 デフォルトのコンストラクターはオブジェクトをゼロにし、デストラクタは何もしません。 ただし、このコンストラクターは、変数の初期化時に明示的に呼び出された場合にのみ生成および使用されます。 明示的な初期化のバリアントを使用しない場合、自明なタイプの変数は初期化されません。 初期化構文は、変数宣言のタイプとコンテキストに依存します。 静的変数とローカル変数は、宣言時に初期化されます。 クラスの場合、直接の基本クラスと非静的クラスメンバーは、コンストラクター初期化リストで初期化されます。 (C ++ 11では、宣言時に非静的クラスメンバーを初期化できます。後述)。動的オブジェクトの場合、式new T()



はデフォルトコンストラクターによって初期化されたオブジェクトを作成しますが、簡易型のnew T



は初期化されていないオブジェクトを作成します。 些細なタイプの動的配列new T[N]



を作成する場合、その要素は常に初期化されません。 std::vector<T>



インスタンスが作成または拡張され、要素の明示的な初期化にパラメーターが提供されない場合、それらは既定のコンストラクターを呼び出すことが保証されます。 C ++ 11では、中括弧を使用した新しい初期化構文が導入されています。 括弧の空のペアは、デフォルトのコンストラクターを使用した初期化を意味します。 このような初期化は、従来の初期化が使用されるすべての場所で可能です。さらに、宣言時にクラスの非静的メンバーを初期化することが可能になり、コンストラクター初期化リストの初期化を置き換えます。







初期化されていない変数は、次のように構成されます。 namespace



スコープ(グローバル)で定義されている場合、すべてのビットがゼロになります。ローカルまたは動的に作成される場合、ランダムなビットセットを受け取ります。 このような変数を使用すると、プログラムの予期しない動作が発生する可能性があることは明らかです。







確かに、最新のコンパイラーでは、進行が止まらないことがあります。場合によっては、初期化されていない変数を検出してエラーをスローします。 さらに良いのは、コードアナライザーによって初期化されていない変数が検出されることです。







C ++ 11標準ライブラリには、タイププロパティ(ヘッダーファイル<type_traits>



)と呼ばれるテンプレートがあります。 そのうちの1つを使用すると、タイプが簡単かどうかを判断できます。 式std::is_trivial<>::value



は、 T



自明な型であればtrue



T



そうでなければfalse



です。







シシル構造は、Plain Old Data(POD)とも呼ばれます。 PODと「自明なタイプ」はほぼ同等の用語であると想定できます。







C#では、初期化されていない変数はエラーを引き起こします;これはコンパイラーによって制御されます。 明示的な初期化が実行されない場合、参照型のオブジェクトのフィールドはデフォルトで初期化されます。 重要なタイプのオブジェクトのフィールドは、デフォルトですべて初期化されるか、明示的にすべて初期化する必要があります。







戦う方法:











3.3。 基本クラスと非静的クラスメンバーの初期化順序



クラスコンストラクターを実装すると、直接の基本クラスと非静的クラスメンバーが初期化されます。 初期化順序は、標準によって決定されます。最初に、基本クラスのリストで宣言されている順序で基本クラス、次に宣言順序でクラスの非静的メンバー。 必要に応じて、基本クラスと非静的メンバーの明示的な初期化では、コンストラクターの初期化リストを使用します。 残念ながら、このリストの項目は、初期化が発生する順序である必要はありません。 初期化中にリストアイテムが他のリストアイテムへの参照を使用する場合、これを考慮する必要があります。 エラーの場合、参照はまだ初期化されていないオブジェクトへの可能性があります。 C ++ 11では、宣言するときに中括弧を使用して非静的クラスメンバーを初期化できます。 この場合、コンストラクターの初期化リストで初期化する必要はなく、問題は部分的に除去されます。







C#では、オブジェクトは次のように初期化されます。最初に、ベースサブオブジェクトから最後の派生物まで、フィールドが初期化され、次にコンストラクターが同じ順序で呼び出されます。 説明されている問題は発生しません。







戦う方法:











3.4。 静的クラスメンバーとグローバル変数の初期化手順



静的クラスメンバ、およびさまざまなコンパイル単位(ファイル)のスコープnamespace



(グローバル)で定義された変数は、実装によって決定された順序で初期化されます。 初期化中にこのような変数が相互に参照を使用する場合、これを考慮する必要があります。 リンクは初期化されていない変数へのリンクである可能性があります。







戦う方法:











3.5。 デストラクタの例外



デストラクタは例外をスローしないでください。 この規則に違反すると、未定義の動作、ほとんどの場合異常終了が発生する可能性があります。







戦う方法:











3.6。 動的オブジェクトと配列の削除



何らかのタイプT



動的オブジェクトT









 T* pt = new T(/* ... */);
      
      





その後、 delete



演算子で削除されます







 delete pt;
      
      





動的配列が作成された場合







 T* pt = new T[N];
      
      





その後、 delete[]



演算子でdelete[]



されます







 delete[] pt;
      
      





この規則に従わない場合、未定義の動作が発生する可能性があります。つまり、メモリリーク、クラッシュなど、あらゆることが起こります。 詳細については、[Meyers1]を参照してください。







戦う方法:











3.7。 クラス宣言が不完全な場合の削除



delete



演算子の雑多さは、特定の問題を引き起こす可能性があります; void*



型のポインターまたは不完全な(プリエンプティブ)宣言を持つクラスへのポインターに適用できます。 クラスへのポインターに適用されるdelete



演算子は2フェーズ操作であり、デストラクタが最初に呼び出され、次にメモリが解放されます。 エラーの宣言が不完全なクラスへのポインターにdelete



演算子を適用すると、エラーは発生せず、コンパイラーは単にデストラクターの呼び出しをスキップします(警告は発行されます)。 例を考えてみましょう:







 class X; //   X* CreateX(); void Foo() {    X* p = CreateX();    delete p; }
      
      





このコードは、完全なX



クラス宣言がdelete



ダイヤルピアで利用できない場合でもコンパイルされます。 Visual Studioは次の警告を表示します。

warning C4150: deletion of pointer to incomplete type 'X'; no destructor called











X



CreateX()



実装がある場合、コードはCreateX()



ますCreateX()



new



演算子によって作成されたオブジェクトへのポインターを返す場合、 Foo()



呼び出しFoo()



正常に実行され、デストラクタは呼び出されません。 これがリソースの流出につながる可能性があることは明らかであるため、警告に注意する必要があることを改めて説明します。







この状況は、決して大げさではありません。スマートポインターや記述子クラスなどのクラスを使用する場合に発生する可能性があります。 このような状況が発生すると、コンパイラによって生成されるデストラクタが刺激される可能性があります。 , , , , . [Meyers2].







:











4. ,





4.1。



++ , . . . , 1.1.







:







 std::out<<c?x:y;
      
      











 (std::out<<c)?x:y;
      
      





じゃない







 std::out<<(c?x:y);
      
      





, , .







. <<



?:



std::out



void*



. ++ , . -, , . ?:



. , ( ).







: x&f==0



x&(f==0)



, (x&f)==0



, , , . - , , , , .







別の例。 / . / , /, . , x/4+1



x>>2+1



, x>>(2+1)



, (x>>2)+1



, .







C# , C++, , - .







:











4.2.



++ , . . , , . 4.1. — +



+=



. . , : ,



(), &&



, ||



。 , (-), (short-circuit evaluation semantics), , . & ( ). & , .. .







, - (-) , . .







- , , . . [Dewhurst].







C# , , , .







:











4.3.



++ , . ( : ,



(), &&



, ||



, ?:



.) , , , . :







 int x=0; int y=(++x*2)+(++x*3);
      
      





y



.







, . 以下に例を示します。





 class X; class Y; void Foo(std::shared_ptr<X>, std::shared_ptr<Y>);
      
      





Foo()



:







 Foo(std::shared_ptr<X>(new X()), std::shared_ptr<Y>(new Y()));
      
      





: X



, Y



, std::shared_ptr<X>



, std::shared_ptr<Y>



. Y



, X



.







:







 auto p1 = std::shared_ptr<X>(new X()); auto p2 = std::shared_ptr<Y>(new Y()); Foo(p1, p2);
      
      





std::make_shared<Y>



( , ):







 Foo(std::make_shared<X>(), std::make_shared<Y>());
      
      





. [Meyers2].







:











5.





5.1。



++98 , ( ), , ( , ). virtual



, , . ( ), , , . , , . , ++11 override



, , , . .







:











5.2。



. , , . . . [Dewhurst].







:











5.3。



, , . , , post_construct pre_destroy. , — . . , : ( ) . (, , .) , ( ), ( ). . [Dewhurst]. , , .







— - .







, C# , , , . C# : , , . , ( , ).







:











5.4。



, , delete



. , - .







:











6.



— C/C++, . . . « ».







C# unsafe mode, .









6.1.



/++ , : strcpy()



, strcat()



, sprinf()



, etc. ( std::vector<>



, etc.) , . (, , , . . Checked Iterators MSDN.) , : , , ; , .







C#, unsafe mode, .







:











6.2. Z-terminated



, . , :







 strncpy(dst,src,n);
      
      





strlen(src)>=n



, dst



(, ). , , . . — . if(*str)



, if(strlen(str)>0)



, . [Spolsky].







C# string



.







:











6.3.



...



. printf



- , C. , , , , . , .







C# printf



, .







:











7.





7.1。



++ , , , . 以下に例を示します。







 const int N = 4, M = 6; int x,                // 1    *px,              // 2    ax[N],            // 3    *apx[N],          // 4    F(char),          // 5    *G(char),          // 6    (*pF)(char),      // 7    (*apF[N])(char),  // 8    (*pax)[N],        // 9    (*apax[M])[N],    // 10    (*H(char))(long);  // 11
      
      





:







  1. int



    ;
  2. int



    ;
  3. N



    int



    ;
  4. N



    int



    ;
  5. , char



    int



    ;
  6. , char



    int



    ;
  7. , char



    int



    ;
  8. N



    , char



    int



    ;
  9. N



    int



    ;
  10. M



    N



    int



    ;
  11. , char



    , long



    int



    .


, . ( .)







*



&



. ( .)







typedef



( using



-). , :







 typedef int(*P)(long); PH(char);
      
      





, .







C# , .







:











7.2。



.







 class X { public:    X(int val = 0); // ... };
      
      











 X x(5);
      
      





x



X



, 5.







 X x();
      
      





x



, X



, x



X



, . X



, , :







 X x; X x = X(); X x{};    //   C++11
      
      





, , , . [Sutter].







, , C++ ( ). . ( C++ .)







, , , , .







C# , , .







:











8.





8.1. inline



ODR



, inline



— . , . inline



(One Defenition Rule, ODR). . , . , ODR. static



: , , . static



inline



. , , ODR, . , . - , -. .







:











8.2.



. . , , , , .







:











8.3. switch







break



case



. ( .) C# .







:











8.4.



++ , — , — . ( class



struct



) , . ( , # Java.) — , .







  1. , . ( std::string



    , std::vector



    , etc.), , .
  2. , , .
  3. , (slicing), , .


, , , . . , , . , . . — ( =delete



), — explicit



.







C# , .







:











8.5.



++ . , . - ( ), ++11 , , , .







C++ .







C# , . , . (using-) Basic Dispose.







:











8.6.



«» . , , C++ , STL- - .







. . , . . «», . COM- . (, .) , C++ . — . . . , («» ) , . .







# , . — .







:











8.7.



C++ , : , , . ( !) . , . , . , , . (, .)







C ( ), C++ C ( extern "C"



). C/C++ .







-. #pragma



- , , .







, , , .







, , COM. COM-, , ( , ). COM , , .







C# . , — , C#, C# C/C++.







:











8.8. マクロ



, . , . C++ . 代わりに







 #define XXL 32
      
      











 const int XXL=32;
      
      





. inline



.







# ( ).







:











9.



  1. . . . , .
  2. .
  3. . ++ — ++11/14/17.
  4. - , - .
  5. .






[Dewhurst]

, . C++. .: . 英語から — .: , 2012.







[Meyers1]

, . C++. 55 .: . 英語から — .: , 2014.







[Meyers2]

, . C++: 42 C++11 C++14.: . 英語から -M。:LLC "I.D. », 2016.







[Sutter]

, . C++.: . 英語から — : «.. », 2015.







[Spolsky]

, . .: . 英語から — .: -, 2008.














All Articles