C ++コードおよび新しいC ++ 0x言語標準の静的分析

注釈

はじめに

1.自動

2. decltype

3. R値の参照

4.直角ブラケット

5.ラムダ関数(ラムダ)

6.接尾辞の戻り型の構文

7. static_assert

8. nullptr

9.新しい標準クラス

10.静的コードアナライザーの開発における新しい方向

おわりに

書誌リスト



注釈



この記事では、C ++ 0x標準で説明され、Visual Studio 2010でサポートされているC ++言語の新機能について説明します。PVS-Studioを例として使用して、言語の変更が静的コード分析ツールに与える影響を調べました。



はじめに



新しいC ++言語標準がまもなく登場します。 C ++ 0xと呼ばれ続けていますが、明らかに最終的な名前はC ++ 11です。 新しい標準は、Intel C ++やVisual C ++などの最新のC ++コンパイラですでに部分的にサポートされています。 サポートは完全とはほど遠いものです。これはごく自然なことです。 第一に、この標準はまだ採用されていません。第二に、採用されたとしても、コンパイラでその機能を実行するには時間がかかります。



新しい標準のサポートが重要なのは、コンパイラ開発者だけではありません。 ソースコードの静的分析のツールでは、言語の革新を迅速にサポートする必要があります。 新しい標準は後方互換性を約束します。 ほとんど保証されており、古いC ++コードは、変更を加えることなく、新しいコンパイラによって正しくコンパイルされます。 ただし、これは、新しい言語構成を含まないプログラムが、新しいC ++ 0x標準をサポートしない静的アナライザーで処理できることを意味するものではありません。 Visual Studio 2010のベータ版で作成されプロジェクトをPVS-Studioで確認することで、実際にこれを確信しました。問題はヘッダーファイルにあり、既に新しい言語構成を使用しています。 たとえば、ヘッダーファイル「stddef.h」では、新しいdecltype演算子の使用を確認できます。

 namespace std {typedef decltype(__ nullptr)nullptr_t;  } 
当然、そのような構造は、C ++ 0xをサポートしていないアナライザーの構文上は正しくなく、その動作の停止または誤った結果につながります。 PVS-StudioはVisual Studio 2010がリリースされるまでにC ++ 0xをサポートする必要があることが明らかになりました。少なくとも、この標準規格がこのコンパイラーでサポートされている限りです。



この問題は私たちによって正常に解決されたと言えます。執筆時点で、サイトにはPVS-Studio 3.50のバージョンがあり、Visual Studio 2005/2008とVisual Studio 2010の両方を統合しています。ツールのバージョンPVS-Studio 3.50以降Visual Studio 2010に実装されているC ++ 0xの一部のサポートが実装されています。たとえば、「右角かっこ」を使用する場合など、サポートは完全ではありませんが、今後のバージョンではC ++ 0x標準のサポートに取り組んでいきます。



この記事では、Visual Studio 2010の第1版でサポートが実装された言語の新機能を検討します。同時に、これらの機能をさまざまな観点から見ていきます。 PVS-Studioでサポートされており、その外観がVivaCoreライブラリにどのように影響したか。



ご注意 VivaCore-コードの解析、分析、変換のためのライブラリ。 VivaCoreはオープンライブラリであり、CおよびC ++言語をサポートしています。 PVS-StudioはVivaCoreに基づいて構築されており、他のソフトウェアプロジェクトはそのベースで作成できます



この記事は、PVS-Studioの新しい標準の研究とサポートに関するレポートと呼ぶことができます。 PVS-Studioツールは、64ビットおよび並列のOpenMPエラーを診断します。 ただし、現在のトピックは64ビットシステムへの移行であるため、PVS-Studioを使用した64ビットエラーの検出を示す例が優先されます。



1.自動



C ++では、Cと同様に、変数の型を明示的に指定する必要があります。 ただし、C ++でのテンプレートタイプとテンプレートメタプログラミングテクニックの出現により、オブジェクトのタイプがそれほど簡単に記述できない状況が頻繁に発生しています。 かなり単純な場合でも、配列の要素を反復処理する場合、次のような反復子型を宣言する必要があります。

 for(vector <int> ::イテレーターitr = myvec.begin(); 
      itr!= myvec.end(); 
      ++ itr) 
このような設計は非常に長く不快です。 typedefを使用してレコードを削減できますが、これにより新しいエンティティが生成され、利便性の面ではほとんど追加されません。



C ++ 0xは、この問題を軽減する方法を提供します。 新しい標準では、autoキーワードの意味が置き換えられます。 以前のautoが変数がスタック上に作成されたことを意味し、他の何か(レジスタなど)を指定しなかった場合に暗黙的に暗示されていた場合、C#3.0のvarに類似しています。 autoとして宣言された変数のタイプは、変数の初期化方法に基づいてコンパイラーによって個別に決定されます。



自動変数は、1つのプログラムの実行中に異なるタイプの値を格納できないことに注意してください。 C ++は依然として静的に型付けされた言語のままであり、autoの指示はコンパイラーに型の決定のみを行うように指示するだけです。初期化後、変数の型を変更することはできなくなります。



これで、イテレータは次のように宣言できます。

 for(自動itr = myvec.begin(); itr!= myvec.end(); ++ itr) 
コードの記述とその簡素化の利便性に加えて、autoキーワードはコードをより安全にするのに役立ちます。 autoが64ビットアプリケーションの作成の観点からコードを安全にする例を見てみましょう。

 bool Find_Incorrect(定数文字列* arrStr、size_t n)
 {
   for(size_t i = 0; i!= n; ++ i)
   {
    符号なしn = arrStr [i] .find( "ABC");
     if(n!= string :: npos)
       trueを返します。
   }
   falseを返します。
 }; 
このコードには64ビットエラーが含まれています。 この関数は、 Win32バージョンをコンパイルすると正しく動作し、 Win64モードでビルドするとクラッシュします。 エラーは、変数「n」に符号なしの型を使用することですが、find()関数が返すtype string :: size_typeを使用する必要があります。 32ビットプログラムでは、型文字列:: size_typeとunsignedが一致し、正しい結果が得られます。 64ビットプログラムでは、文字列:: size_typeと符号なしは一致しなくなります。 部分文字列が見つからない場合、find()関数は、0xFFFFFFFFFFFFFFFFFFuiui64に等しい文字列:: nposを返します。 この値は0xFFFFFFFFuに切り捨てられ、32ビット変数に配置されます。 その結果、条件0xFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFui64はfalseであり、Find_Incorrect関数は常にtrueを返します。



この例では、エラーはそれほど恐ろしくありません。コンパイラーおよびより専門的なViva64アナライザー(PVS-Studioに含まれています)でも検出されます。



コンパイラ

警告C4267: '初期化中': 
 「size_t」から「unsigned int」への変換、データの損失の可能性 
Viva64:

 V103:暗黙的にmemsizeから32ビット型への型変換。 
さらに重要なことは、このエラーが発生する可能性があり、戻り値を格納するための型を選択するときのずさんなために、コードでよく発生します。 厄介な形式の文字列:: size_typeを使用したくないためにエラーが発生した可能性さえあります。



現在、このようなエラーは、コードを乱雑にすることなく簡単に回避できます。 「auto」タイプを使用すると、次のシンプルで信頼性の高いコードを作成できます。

 auto n = arrStr [i] .find( "ABC");
 if(n!= string :: npos)
   trueを返します。 
エラーは自動的に消えました。 コードが複雑になったり、効率が低下したりしていません。 結論-「自動」の使用は多くの場合合理的です。



キーワード「auto」は、64ビットエラーの数を減らすか、エラーをよりエレガントに修正できるようにします。 ただし、「自動」だけを使用しても、64ビットエラーがすべて排除されるわけではありません。 これは、プログラマの作業を楽にする別の言語ツールですが、型制御のすべての作業を行うわけではありません。 例を考えてみましょう:

 void * AllocArray3D(int x、int y、int z、
                    size_t objectSize)
 {
   intサイズ= x * y * z * objectSize;
   return malloc(サイズ);
 } 
関数は、配列のサイズを計算し、必要なメモリ量を割り当てる必要があります。 64ビット環境では、この関数がdouble型のサイズ2000 * 2000 * 2000の配列を操作するためのメモリを割り当てることができると期待するのは論理的です。 ただし、「AllocArray3D(2000、2000、2000、sizeof(double));」という形式の呼び出しは、そのような量のメモリを割り当てることが不可能であるかのように、常にNULLを返します。 関数がNULLを返す本当の理由は、式「int size = x * y * z * sizeof(double)」のオーバーフローエラーです。 変数「サイズ」は値-424509440を取り、malloc関数をさらに呼び出しても意味がありません。 ところで、コンパイラはこの式の危険性について警告します:

警告C4267: '初期化中': 
 「size_t」から「int」への変換、データの損失の可能性 
「自動」を期待して、ずさんなプログラマーは次のようにコードを変更できます。

 void * AllocArray3D(int x、int y、int z、
                    size_t objectSize)

 {
  自動サイズ= x * y * z * objectSize;
   return(double *)malloc(size);
 } 
ただし、これはまったく排除せず、エラーを偽装するだけです。 コンパイラは警告を発行しなくなりますが、AllocArray3D関数は引き続きNULLを返します。



変数「size」のタイプは自動的に「 size_t 」になります。 ただし、式「x * y * z」を評価するとオーバーフローが発生します。 この部分式のタイプは「int」であり、変数「objectSize」を乗算すると、そのタイプのみが「size_t」に展開されます。



現在、この隠れたエラーはViva64アナライザーを使用してのみ検出できます。

 V104:memsize型への暗黙的な型変換 
算術式。 
結論-「自動」を使用しても、まだ注意が必要です。



次に、Viva64静的アナライザーが構築されているVivaCoreライブラリで新しいキーワードがどのようにサポートされているかを簡単に見てみましょう。 したがって、アナライザーは、変数AAが「size_t」型に展開されることを警告するために、変数AAが「int」型であることを理解できるはずです( V101を参照)。

 void Foo(int X、int Y)
 {
   auto AA = X * Y;
   size_t BB = AA;  // V101
 } 
まず、新しいC ++ 0xキーワードを含む新しいトークンテーブルがコンパイルされました。 このテーブルはLex.ccファイルにあり、名前はtableC0xxです。 トークン「auto」(tkAUTO)を処理するための古いコードを変更しないために、このテーブルのトークン「auto」の名前はtkAUTOcpp0xです。



新しいトークンの出現に関連して、次の関数が変更されました:isTypeToken、optIntegralTypeOrClassSpec。 新しいLeafAUTOc0xxクラスが登場しました。 TypeInfoIdは、新しいオブジェクトクラスのAutoDecltypeTypeを導入しました。



タイプ「auto」のコーディングでは、文字「x」が選択されました。これは、TypeInfoクラスとEncodingクラスの機能に反映されます。 これらは、たとえば、IsAutoCpp0x、MakePtreeなどの関数です。



これらの修正により、キー「auto」を使用してコードを解析できるようになりました。これは新しい意味を持ち、エンコードされた形式(「x」の文字)でオブジェクトのタイプを保存します。 ただし、これにより、変数が実際にどの型を表しているかを知ることはできません。 つまり、VivaCoreには、「auto AA = X * Y」という式で変数AAが「int」型であることがわかる機能がありません。



この機能はViva64ソースコードに含まれており、VivaCoreライブラリコードには含まれていません。 原則は、TranslateAssignInitializerメソッドで型を計算する余分な作業です。 式の右辺が計算された後、変数名と型のバインドが置き換えられます。



2. decltype



場合によっては、オブジェクトのタイプを「コピー」すると便利です。 「auto」キーワードは、変数の初期化に使用される式に基づいて型を推測します。 初期化がない場合は、キーワード「decltype」を使用して、コンパイル時に式のタイプを決定できます。 変数「値」が関数「Calc()」によって返される型を持つサンプルコード:

 decltype(Calc())値;
 {
  値= Calc(); 
 }
 catch(...){
  投げる
 } 
decltypeを使用して、型を宣言できます。

 void f(定数ベクトル<int>&a、
       ベクトル<float>&b)
 {
   typedef decltype(a [0] * b [0])Tmp;
   for(int i = 0; i <b.size(); ++ i)
   {
     Tmp * p =新しいTmp(a [i] * b [i]);
     // ...
   }
 } 
decltypeを使用して取得される型は、autoを使用して推定される型と異なる場合があることに注意してください。

 const std :: vector <int> v(1);
 auto a = v [0]; decltype(v [0])b = 1;
 //タイプa-int  
 //タイプb-const int&(戻り値
 // std :: vector <int> :: operator [](size_type)const) 
64ビットの観点から「decltype」が役立つ例に移りましょう。 IsPresent関数は、シーケンス内の要素を検索し、見つかった場合はtrueを返します。

 bool IsPresent(char *配列、
                size_t arraySize、
               文字キー)
 {
   for(unsigned i = 0; i <arraySize; i ++) 
     if(配列[i] ==キー)
       trueを返します。
   falseを返します。
 } 
この関数は、大きな配列を持つ64ビットシステムでは機能しません。 変数arraySizeの値がUINT_MAXより大きい場合、条件「i <arraySize」は満たされず、永遠のサイクルが発生します。



キーワード「auto」を使用する場合、これは何も変更しません。

 for(auto i = 0; i <arraySize; i ++) 
   if(配列[i] ==キー)
     trueを返します。 
0は "int"型であるため、変数 "i"は "int"型になります。 正しい修正方法は、「decltype」を使用することです。

 for(decltype(arraySize)i = 0; i <arraySize; i ++) 
   if(配列[i] ==キー)
     trueを返します。 
現在、カウンタ「i」のタイプは「size_t」で、変数「arraySize」です。



VivaCoreライブラリでのdecltypeのサポートは、autoのサポートに非常に似ています。 新しいtkDECLTYPEトークンが追加されました。 Parser.ccファイルに解析関数rDecltypeを追加しました。 新しいトークンの出現に関連して、optIntegralTypeOrClassSpec関数が変更されました。 新しいLeafDECLTYPEクラスが登場しました。



decltype演算子によって返されるタイプをエンコードするには、文字「X」が選択されます(自動に使用される大文字の「x」ではなく、大文字の「X」)。 この点で、TypeInfoおよびEncodingクラスの機能が変更されました。 たとえば、WhatIs、IsDecltype、MakePtree関数。



decltype演算子の型計算機能は、Environmentクラスに実装されており、VivaCoreライブラリの一部です。 型の計算は、環境に新しい変数/型を書き込むときに実行されます(関数RecordTypedefName、RecordDeclarator、RecordConstantDeclarator)。 タイプの計算は、FixIfDecltype関数によって実行されます。



3. R値の参照



C ++ 98標準では、一時オブジェクトは関数で渡すことができますが、定数参照(const&)としてのみ渡すことができます。 したがって、この関数は、それが一時的か通常かを判別できません。これはconst&としても渡されます。



C ++ 0xでは、新しいタイプのリンク-一時オブジェクトへのリンク(R値参照)が追加されます。 彼の発表は次のとおりです。「TYPE_NAME &&」。 一定ではない、法的に変更可能なオブジェクトとして使用できます。 この革新により、一時オブジェクトを考慮し、転送セマンティクス(移動セマンティクス)を実装できます。 たとえば、std :: vectorが一時オブジェクトとして作成されるか、関数から返される場合、新しいオブジェクトを作成することにより、新しいタイプのリンクからすべての内部データを転送できます。 転送コンストラクタstd ::結果の一時オブジェクトへのリンクを介したベクトルは、リンクにある配列へのポインタをコピーするだけで、最後に空の状態に設定されます。



ハイフンコンストラクターまたはハイフンは、次のように宣言できます。

テンプレート<クラスT>クラスベクトル{
   // ...
  ベクトル(定数ベクトル&);  //コンストラクタをコピーします
  ベクトル(ベクトル&&);  //コンストラクタを移動します
  ベクトル&演算子=(定数ベクトル&);  //割り当てをコピーします
  ベクトル&演算子=(ベクトル&&);  //割り当てを移動します
 }; 


コード内の64ビットエラーを分析するという観点から見ると、それは重要ではなく、タイプ「&」または「&&」を宣言するときに処理されます。 したがって、VivaCoreでのこのイノベーションのサポートは非​​常に簡単です。 変更は、ParserクラスのoptPtrOperator関数のみに影響しました。 その中で、「&」と「&&」の両方を等しく認識します。



4.直角ブラケット



C ++ 98標準の観点から、次の構成には構文エラーが含まれています。

 list <vector <string >> lvs; 
それを防ぐには、2つの右閉じ山括弧の間にスペースを挿入する必要があります。

 list <vector <string >> lvs; 
C ++ 0x標準では、テンプレートタイプを宣言するときに、間にスペースを挿入することなく、二重閉じ括弧の使用が合法化されました。 その結果、もう少しエレガントなコードを書くことが可能になります。



開発者は喜んで余分なスペースを書くのをやめるので、この革新のサポートは静的アナライザーに実装することが重要です。



現時点では、「>>」を使用したテンプレートタイプの宣言の解析は、VivaCoreに最適な方法でまだ実装されていません。 場合によっては、アナライザーが誤っており、明らかに、時間の経過とともに、テンプレートの分析に関連するアナライザー部品が大幅に手直しされます。 コードでは、ヒューリスティックメソッドが決定しようとする次のい関数を確認できますが、シフト演算子「>>」またはテンプレートタイプ「A <B <C >> D」の宣言の一部を処理しています:IsTemplateAngleBrackets、isTemplateArgs。 この問題の解決策に正しくアプローチする方法に興味がある人には、次のドキュメントが役立ちます。「 直角ブラケット(N1757) 」 時間が経つにつれて、VivaCoreの直角ブラケットの処理が改善されます。



5.ラムダ関数(ラムダ)



C ++ラムダ式は、匿名ファンクター(関数として使用できるオブジェクト)を記述するための短い形式です。 少し歴史を考えてみましょう。 Cでは、関数ポインターを使用してファンクターを作成します。

 / *コールバック関数* /
 int compare_function(int A、int B){
   return A <B;
 }
 
 / *ソート関数の宣言* /
 void mysort(int * begin_items、
             int num_items、
             int(* cmpfunc)(int、int));
 
 int main(void){
     int items [] = {4、3、1、2};
     mysort(アイテム、
            sizeof(アイテム)/ sizeof(int)、
            compare_function);
     0を返します。
 } 
以前のC ++では、演算子()がオーバーロードされたクラスを使用してファンクターが作成されました。

クラスcompare_class {
  公開:
   bool operator()(int A、int B){
     return(A <B);
   }
 };
 	
 //ソート関数を宣言します
テンプレート<class ComparisonFunctor> 
 void mysort(int * begin_items、
              int num_items、
              ComparisonFunctor c);
 
 int main(){
     int items [] = {4、3、1、2};
     compare_classファンクター;
     mysort(アイテム、
     sizeof(アイテム)/ sizeof(int)、
    ファンクター);
 } 
C ++ 0xでは、ファンクターをさらにエレガントに宣言する機会が得られます。

 auto compare_function = [](char a、char b)
   {<bを返す;  };

 char Str [] = "cwgaopzq";
 std :: sort(Str、
           Str + strlen(Str)、
           compare_function);
 cout << Str << endl; 
ファンクタである変数compare_functionを開始し、そのタイプはコンパイラによって自動的に決定されます。 次に、この変数をstd :: sortに渡すことができます。 コードをさらに削減できます。

 char Str [] = "cwgaopzq";
 std :: sort(
   Str
   Str + strlen(Str)、
   [](char a、char b){aを返す<b;}
 );
 cout << Str << endl; 
ここで、「[](char a、char b){return a <b;}」は、ラムダ関数にすぎません。



ラムダ式は常に角かっこ[]で始まり、キャプチャリストを指定できます。 次に、オプションのパラメーターリストとオプションの戻り値の型があります。 関数本体への宣言を直接完了します。 一般に、ラムダ関数を記述するための形式は次のとおりです。

 '[' [<キャプチャリスト>] ']' '
 ['(' <パラメータリスト> ')' ['mutable']]
 ['throw' '(' [<exception types>] ')']
 ['->' <return_type_type>]
 '{' [<関数本体>] '}' 
キャプチャリストは、外部スコープのどのオブジェクトがラムダ関数によってアクセス可能かを示します。

残念ながら、この記事のフレームワーク内では、ラムダ関数にもっと注意を向けることはできません。 ラムダ関数の詳細については、記事の最後にある参考文献に記載されているリソースをご覧ください。 ラムダ関数の使用のデモンストレーションとして、長さの昇順で文字列を表示するプログラムのコードを検討してください。



プログラムは、文字列の配列とインデックスの配列を作成します。 次に、プログラムは行のインデックスをソートして、行の長さを増やして行を配置します。

 int _tmain(int、_TCHAR * [])
 {
  ベクター<string>文字列。
   strings.push_back( "lambdas");
   strings.push_back( "decltype");
   strings.push_back( "auto");
   strings.push_back( "static_assert");
   strings.push_back( "nullptr");

  ベクトル<size_t>インデックス。
   size_t k = 0;
   generate_n(back_inserter(インデックス)、
              strings.size()、
              [&k](){k ++を返す;  });

  ソート(indices.begin()、
        indexs.end()、
        [&](ptrdiff_t i1、ptrdiff_t i2)
        {文字列を返す[i1] .length()<
                文字列[i2] .length();  });

   for_each(indices.begin()、
            indexs.end()、
            [&文字列](const size_t i)
            {cout <<文字列[i] << endl;  });

   0を返します。
 } 
注:C ++ 0xによると、次のようにstd :: vector配列を初期化できます。

ベクトル<size_t>インデックス= {0,1,2,3,4}; 
ただし、Visual Studio 2010はそのような設計をサポートしていません。



静的アナライザーでのラムダ関数の分析の品質は、単純な関数の分析の品質に対応する必要があります。 一般に、ラムダ関数の分析は、ラムダ関数のスコープが異なることを除いて、単純な関数の分析に似ています。



PVS-Studioは、ラムダ関数で本格的なエラー診断を実装しています。 64ビットエラーを含むコード例を考えてみましょう。

 int a = -1;
符号なしb = 0;
 const char str [] = "Viva64";
 const char * p = str + 1;

 auto lambdaFoo = [&]()-> char
   {
     return p [a + b];
   };

 cout << lambdaFoo()<< endl; 
このコードは、Win32モードでコンパイルするときに機能し、画面に文字「V」を出力します。 Win64モードでは、番号0xFFFFFFFFの要素にアクセスしようとするため、プログラムがクラッシュします。 このタイプのエラーの詳細については、64ビットC / C ++アプリケーションの開発に関するレッスン -「 レッスン13.パターン5.アドレス演算 」を参照してください。



上記のコードを確認すると、PVS-Studioは診断メッセージを表示します。

エラーV108:インデックスタイプが正しくありません:p [memsize-typeではありません]。 代わりにmemsizeタイプを使用してください。 
したがって、アナライザーはラムダ関数を解析し、変数のスコープを処理する必要がありました。 難しいが必要な機能。



ラムダ関数のサポートにより、VivaCoreの最も重要な変更が結び付けられます。 解析ツリーを構築するプロセスでは、新しいrLambdas関数が関係しています。 この関数はParserクラスにあり、rInitializeExpr、rFunctionArguments、rCommaExpressionなどの関数から呼び出されます。 rLambdas関数はラムダ関数を解析し、ツリーに新しいオブジェクトタイプPtreeLambdaを追加します。 PtreeLambdaクラスは、PtreeLambda.hおよびPtreeLambdaファイルで宣言および実装されます。



構築されたツリーでのPtreeLambda処理は、TranslateLambda関数によって実行されます。 ラムダ関数を操作するためのすべてのロジックは、VivaCoreに集中しています。 TranslateLambda内では、PVS-Studioコードに実装されているGetReturnLambdaFunctionTypeForReturn関数の呼び出しが発生します。 ただし、この関数はPVS-Studioによって内部目的で使用され、VivaCoreの空のGetReturnLambdaFunctionTypeForReturn関数プラグインは、コードの分析に一切影響しません。



6.接尾辞の戻り型の構文



関数によって返される型を指定するのが難しい場合があります。 2つの値を乗算するテンプレート関数の例を考えてみましょう。

テンプレート<クラスT、クラスU>
 ???  mul(T x、U y)
 {
   return x * y;
 } 
戻りタイプは、式「x * y」のタイプでなければなりません。 しかし、「???」の代わりに何を書くことができるかは明確ではありません。 最初のアイデアは、「decltype」を使用することです。

テンプレート<クラスT、クラスU>
 decltype(x * y)mul(T x、U y)//スコープの問題!
 {
   return x * y;
 } 
変数 "x"と "y"は "decltype(x * y)"の後に宣言されており、残念ながらそのようなコードはコンパイルできません。



この問題の解決策は、戻り値の新しい構文を使用することです。

テンプレート<クラスT、クラスU>
 [] mul(T x、U y)-> decltype(x * y)
 {
   return x * y;
 } 
角かっこ[]を使用して、ここでラムダ関数を生成し、同時に「戻り値の型は後で推測または指定されます」と言います。 残念ながら、上記の例は正しいものの、この記事の執筆時点ではVisual C ++でコンパイルされていません。 ただし、別の方法を使用することもできます(サフィックスの戻り値の構文も使用されます)。

テンプレート<クラスT、クラスU>
 auto mul(T x、U y)-> decltype(x * y)
 {
   return x * y;
 } 
このコードはVisual C ++によって正常にビルドされ、目的の結果が得られます。



バージョンPVS-Studio 3.50では、新しい関数形式のサポートは部分的にのみ実装されています。 VivaCoreライブラリは構造を完全に理解しますが、PVS-Studioは分析時にこれらの関数によって返されるデータ型を考慮しません。 Parser :: rIntegralDeclaration関数で、VivaCoreライブラリの関数の代替記録のサポートに慣れることができます。



7. static_assert



C ++ 0x標準では、新しいstatic_assertキーワードが登場しました。 構文:

 static_assert(式、「エラーメッセージ」); 
式が偽の場合、指定されたエラーメッセージが表示され、コンパイルが停止します。 static_assertの使用例を考えてみましょう。

テンプレート<unsigned n>
 struct MyStruct
 {
   static_assert(n> 5、「Nは5以上でなければなりません」);
 };

 MyStruct <3> obj; 
このコードをコンパイルすると、Visual C ++コンパイラは次のメッセージを表示します。

エラーC2338:Nは5以上でなければなりません
   xx.cpp(33):クラステンプレートへの参照を参照
  インスタンス化 'MyStruct <n>'がコンパイルされています 
  と
   [
     n = 3
   ] 
PVS-Studioによって実行されるコード分析の観点からは、static_assertコンストラクトは重要ではないため、無視されます。 VivaCoreは新しいtkSTATIC_ASSERTトークンを追加しました。 このトークンに遭遇すると、レクサーはこのトークンとstatic_assertコンストラクトに関連するすべてのパラメーターを無視します(Lex :: ReadToken関数での実装)。



8. nullptr



C ++ 0x標準以前は、C ++にはヌルポインターを示すキーワードがありませんでした。 数字0はそれを示すために使用されましたが、NULLマクロの使用は良いスタイルと見なされました。 展開すると、NULLマクロは0になり、実用的な違いはありません。 これは、Visual StudioでNULLマクロが宣言される方法です。

 #define NULL 0 
場合によっては、nullポインターを示す特別なキーワードがないことが不便であり、エラーを引き起こしました。 例を考えてみましょう:

 void Foo(int a)
 {cout << "Foo(int a)" << endl;  }

 void foo(char * a)
 {cout << "Foo(char * a)" << endl;  }

 int _tmain(int、_TCHAR * [])
 {
  フー(0);
   Foo(NULL);
   0を返します。
 } 
プログラマはこのコードでFooのさまざまな関数が呼び出されることを期待できますが、そうではありません。 NULLの代わりに、0が「int」のタイプに置き換えられ、プログラムの起動時に画面に出力されます。

フー(int a)
フー(int a) 
そのような状況を排除するために、nullptrキーワードがC ++ 0xで導入されました。 nullptr定数はnullptr_t型であり、暗黙的に任意の型のポインターまたはクラスメンバーへのポインターにキャストされます。 nullptr定数は、bool型を除き、暗黙的に整数データ型にキャストされません。



例に戻り、nullptr引数を使用して「Foo」関数の呼び出しを追加しましょう。

 void Foo(int a)
 {cout << "Foo(int a)" << endl;  }

 void foo(char * a)
 {cout << "Foo(char * a)" << endl;  }

 int _tmain(int、_TCHAR * [])
 {
  フー(0);
   Foo(NULL);
   Foo(nullptr);
   0を返します。
 } 
画面が印刷されます:

フー(int a)
フー(int a)
フー(char * a) 
nullptrキーワードは、64ビットエラーの検索の観点からは興味深いものではありませんが、コードの解析時にはそのサポートが必要です。 これを行うために、新しいトークンtkNULLPTRがVivaCoreに追加され、LeafNULLPTRクラスも追加されました。 LeafNULLPTR型のオブジェクトは、rPrimaryExpr関数で作成されます。 LeafNULLPTR :: Typeof関数が呼び出されると、タイプ「nullptr」は「Pv」、つまり「void *」としてエンコードされます。 PVS-Studioの既存のコード分析タスクの観点からは、これで十分です。



9.新しい標準クラス



C ++ 0x標準では、名前空間stdに関連する新しい標準クラスが導入されています。 これらのクラスの多くは、Visaul Studio 2010で既にサポートされています。例は次のとおりです。



リストされたエンティティは通常のテンプレートクラスであるため、それらの外観はPVS-StudioまたはVivaCoreライブラリの変更を必要としませんでした。



10.静的コードアナライザーの開発における新しい方向



最後に、C ++ 0xの使用に関連する1つの興味深い点に言及したいと思います。 一方で、古い欠陥を修正する言語の新しい機能は、コードをより安全かつ効率的にしますが、同時に、プログラマーが陥る可能性のある新しい未知のトラップも作成します。 確かに、それらについてはまだ何も言えません。



ただし、新しいC ++ 0x構造での診断がはるかに悪い実装であるか、まったく実装されていないという事実により、既知のトラップに陥ることがあります。 初期化されていない変数の使用を示す小さな例を考えてみましょう。

 {
   int x;
   std :: vector <int> A(10);
   A [0] = x;  //警告C4700
 }

 {
   int x;
   std :: vector <int> A(10);
   std :: for_each(A.begin()、A.end()、
     [x](int&y)
     {y = x;  } //警告なし
   );
 } 
プログラマーは、最初のケースと2番目のケースの両方でコンパイラーから警告を受け取ることを期待できます。 ただし、ラムダ関数を使用した例では、警告は発行されません(Visual Studio 2010 RC、/ W4でテスト済み)。 以前にはなかったように、さまざまな危険な状況に関する他の多くの警告。 詳細な診断を実装するには時間がかかります。



C ++ 0xコンストラクトを使用した場合に発生する可能性のある危険なコンストラクトの検索に関して、静的アナライザーの新しいラウンドの開発が期待できます。 製品PVS-Studioは、最新のプログラムをチェックするためのツールとして位置付けられています。 現時点では、これにより64ビットおよび並列技術を意味します。 将来的には、C ++ 0xを使用した場合に予想される潜在的な問題について調査する予定です。 落とし穴が多い場合は、おそらく診断用の新しいツールの作成を開始します。



おわりに



私たちの意見では、C ++ 0xは多くの良い点をもたらします。 古いコードはすぐに近代化する必要はありませんが、リファクタリング中に時間をかけて修正することができます。 新しいコードは、新しいコンストラクトを使用してすでに記述されている場合があります。 したがって、C ++ 0xの使用を開始することは、現時点では合理的です。



書誌リスト



  1. Bjarne Stroustrup。 C ++ 0x-次のISO C ++標準。 http://www.viva64.com/go.php?url=304
  2. Visual C ++チームのブログ。 右辺値参照:VC10のC ++ 0xの機能、パート2。http://www.viva64.com/go.php?url = 305
  3. セルゲイ・オレンダレンコ。 C ++ 0x。 ラムダ式。 http://www.viva64.com/go.php?url=306
  4. マキシム。 C ++ 0xおよび初期化問題の解決。 http://www.viva64.com/go.php?url=307
  5. ウィキペディア C ++ 0x。 http://www.viva64.com/go.php?url=301



All Articles