フォードを知らない、水に入ってはいけない。 パート1

フォードを知らない、水に入ってはいけない

C / C ++プログラマーが火を疑わずにどのようにプレイするかについて、いくつかの小さなメモを書きたかったのです。 最初の注意点は、コンストラクターを明示的に呼び出すことです。



プログラマーは怠け者です。 したがって、彼らは最小限のコードで問題を解決しようと努力しています。 それは称賛に値する良い努力です。 主なことは、プロセスに夢中になって時間内に停止しないことです。



たとえば、プログラマーは、クラス内に単一の初期化関数を作成して別のコンストラクターから呼び出すのは面倒です。 プログラマーは、「なぜ追加の機能が必要なのですか? あるコンストラクターを別のコンストラクターと呼びたいです。」 残念ながら、この単純なタスクでさえ、プログラマーが常に解決できるわけではありません。 このような失敗した試行を特定するために、 PVS-Studioで新しいルールを実装しています。 eMuleプロジェクトで見つけたコードの例を次に示します。

 クラスCSlideBarGroup
 {
公開:
   CSlideBarGroup(CString strName、
     INT iIconIndex、CListBoxST * pListBox);
   CSlideBarGroup(CSlideBarGroup&Group);
   ...
 }

 CSlideBarGroup :: CSlideBarGroup(CSlideBarGroupおよびグループ)
 {
   CSlideBarGroup(
     Group.GetName()、Group.GetIconIndex()、Group.GetListBox());
 } 


最後のコンストラクターの実装を慎重に検討してください。 プログラマーはコードを

  CSlideBarGroup(
   Group.GetName()、Group.GetIconIndex()、Group.GetListBox()); 


別のコンストラクタを呼び出すだけです。 種類はありません。 ここでは、CSlideBarGroup型の名前のない新しいオブジェクトが作成され、すぐに破棄されます。



プログラマーが実際に別のコンストラクターを呼び出したことがわかりました。 しかし、彼は意図したことをまったくしませんでした。 クラスフィールドは初期化されません。



そのような間違いは問題の半分にすぎません。 別のコンストラクタを実際に呼び出す方法を知っている人もいます。 そして原因。 彼らはこれを行う方法を知らなかった場合、それは良いでしょう。 :)



たとえば、上記のコードは次のように書き換えることができます。

  CSlideBarGroup :: CSlideBarGroup(CSlideBarGroupおよびグループ)
 {
   this-> CSlideBarGroup :: CSlideBarGroup(
     Group.GetName()、Group.GetIconIndex()、Group.GetListBox());
 } 


または:

  CSlideBarGroup :: CSlideBarGroup(CSlideBarGroupおよびグループ)
 {
  新しい(これ)CSlideBarGroup(
     Group.GetName()、Group.GetIconIndex()、
     Group.GetListBox());
 } 


データを初期化する1つのコンストラクターが別のコンストラクターを呼び出します。



これを行うプログラマを見つけたら、彼の前に1つの棚と私から個人的に1つの棚を与えてください。



上記の例は非常に危険なコードであり、どのように機能するかを理解する必要があります!



ささいな最適化(独立した関数を書くのが面倒)のため、このコードは良いことよりも害を及ぼすことがあります。 このような設計が機能する場合もあるが、そうでない場合も多い理由を詳しく考えてみましょう。

 クラスSomeClass
 {
   int x、y;
公開:
   SomeClass(){new(this)SomeClass(0,0);  }
   SomeClass(int xx、int yy):x(xx)、y(yy){}
 }; 


このコードは正しく機能します。 クラスには単純なデータ型が含まれており、他のクラスから継承されないため、コードは安全で動作します。 この場合、コンストラクターを2回呼び出しても何も脅威になりません。



明示的なコンストラクター呼び出しでエラーが発生する別のコードを考えます(例はStackOverflow Webサイトでの議論から取られています)。

 クラスベース 
 { 
公開: 
  char * ptr; 
  std :: vector vect; 
 ベース(){ptr = new char [1000];  } 
  〜ベース(){delete [] ptr;  } 
 }; 
 
派生クラス:ベース 
 { 
  派生(Foo foo){} 
  派生(バーバー){ 
      new(this)Derived(bar.foo); 
   } 
 } 


コンストラクタを「new(this)Derived(bar.foo);」と呼ぶと、Baseオブジェクトがすでに作成されており、フィールドが初期化されています。 コンストラクターを呼び出すと、二重の初期化が行われます。 「ptr」に、新しく割り当てられたメモリへのポインタを書き込みます。 その結果、メモリリークが発生します。 std :: vector型のオブジェクトの二重初期化が何につながるかを予測することは困難です。 一つのことは明らかです。 このコードは無効です。



おわりに



明示的なコンストラクター呼び出しは、非常にまれな場合にのみ必要です。 従来のプログラミングでは、通常、コードサイズを小さくしたいため、明示的なコンストラクター呼び出しが表示されます。 これをしないでください! 通常の初期化関数を作成します。

 正しいコードは次のようになります。
クラスCSlideBarGroup
 {
   void Init(CString strName、INT iIconIndex、
             CListBoxST * pListBox);
公開:
   CSlideBarGroup(CString strName、INT iIconIndex、
                  CListBoxST * pListBox)
   {
     Init(strName、iIconIndex、pListBox);
   }
   CSlideBarGroup(CSlideBarGroupおよびグループ)
   {
     Init(Group.GetName()、Group.GetIconIndex()、
          Group.GetListBox());
   }
   ...
 }; 



All Articles