はじめに
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; } |
この問題は私たちによって正常に解決されたと言えます。執筆時点で、サイトには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) |
C ++ 0xは、この問題を軽減する方法を提供します。 新しい標準では、autoキーワードの意味が置き換えられます。 以前のautoが変数がスタック上に作成されたことを意味し、他の何か(レジスタなど)を指定しなかった場合に暗黙的に暗示されていた場合、C#3.0のvarに類似しています。 autoとして宣言された変数のタイプは、変数の初期化方法に基づいてコンパイラーによって個別に決定されます。
自動変数は、1つのプログラムの実行中に異なるタイプの値を格納できないことに注意してください。 C ++は依然として静的に型付けされた言語のままであり、autoの指示はコンパイラーに型の決定のみを行うように指示するだけです。初期化後、変数の型を変更することはできなくなります。
これで、イテレータは次のように宣言できます。
for(自動itr = myvec.begin(); itr!= myvec.end(); ++ itr) |
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を返します。 }; |
この例では、エラーはそれほど恐ろしくありません。コンパイラーおよびより専門的なViva64アナライザー(PVS-Studioに含まれています)でも検出されます。
コンパイラ
警告C4267: '初期化中': 「size_t」から「unsigned int」への変換、データの損失の可能性 |
V103:暗黙的にmemsizeから32ビット型への型変換。 |
現在、このようなエラーは、コードを乱雑にすることなく簡単に回避できます。 「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(サイズ); } |
警告C4267: '初期化中': 「size_t」から「int」への変換、データの損失の可能性 |
void * AllocArray3D(int x、int y、int z、 size_t objectSize) { 自動サイズ= x * y * z * objectSize; return(double *)malloc(size); } |
変数「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 } |
新しいトークンの出現に関連して、次の関数が変更されました: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(...){ 投げる } |
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]); // ... } } |
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) |
bool IsPresent(char *配列、 size_t arraySize、 文字キー) { for(unsigned i = 0; i <arraySize; i ++) if(配列[i] ==キー) trueを返します。 falseを返します。 } |
キーワード「auto」を使用する場合、これは何も変更しません。
for(auto i = 0; i <arraySize; i ++) if(配列[i] ==キー) trueを返します。 |
for(decltype(arraySize)i = 0; i <arraySize; i ++) if(配列[i] ==キー) trueを返します。 |
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; |
list <vector <string >> lvs; |
開発者は喜んで余分なスペースを書くのをやめるので、この革新のサポートは静的アナライザーに実装することが重要です。
現時点では、「>>」を使用したテンプレートタイプの宣言の解析は、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を返します。 } |
クラス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)、 ファンクター); } |
auto compare_function = [](char a、char b) {<bを返す; }; char Str [] = "cwgaopzq"; std :: sort(Str、 Str + strlen(Str)、 compare_function); cout << Str << endl; |
char Str [] = "cwgaopzq"; std :: sort( Str Str + strlen(Str)、 [](char a、char b){aを返す<b;} ); cout << Str << endl; |
ラムダ式は常に角かっこ[]で始まり、キャプチャリストを指定できます。 次に、オプションのパラメーターリストとオプションの戻り値の型があります。 関数本体への宣言を直接完了します。 一般に、ラムダ関数を記述するための形式は次のとおりです。
'[' [<キャプチャリスト>] ']' ' ['(' <パラメータリスト> ')' ['mutable']] ['throw' '(' [<exception types>] ')'] ['->' <return_type_type>] '{' [<関数本体>] '}' |
- []-外部スコープから変数をキャプチャせずに。
- [=]-すべての変数は値によってキャプチャされます。
- [&]-すべての変数は参照によってキャプチャされます。
- [x、y]-値によるxおよびyのキャプチャ。
- [&x、&y]-参照によりxとyをキャプチャします。
- [in、&out]-値で取り込み、out-参照で取り込み。
- [=、&out1、&out2]-参照によってキャプチャされるout1とout2を除く、値によるすべての変数のキャプチャ。
- [&、x、&y]-xを除くすべての変数の参照によるキャプチャ。
プログラムは、文字列の配列とインデックスの配列を作成します。 次に、プログラムは行のインデックスをソートして、行の長さを増やして行を配置します。
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を返します。 } |
ベクトル<size_t>インデックス= {0,1,2,3,4}; |
静的アナライザーでのラムダ関数の分析の品質は、単純な関数の分析の品質に対応する必要があります。 一般に、ラムダ関数の分析は、ラムダ関数のスコープが異なることを除いて、単純な関数の分析に似ています。
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; |
上記のコードを確認すると、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; } |
テンプレート<クラスT、クラスU> decltype(x * y)mul(T x、U y)//スコープの問題! { return x * y; } |
この問題の解決策は、戻り値の新しい構文を使用することです。
テンプレート<クラスT、クラスU> [] mul(T x、U y)-> decltype(x * y) { return x * y; } |
テンプレート<クラスT、クラスU> auto mul(T x、U y)-> decltype(x * y) { return x * y; } |
バージョンPVS-Studio 3.50では、新しい関数形式のサポートは部分的にのみ実装されています。 VivaCoreライブラリは構造を完全に理解しますが、PVS-Studioは分析時にこれらの関数によって返されるデータ型を考慮しません。 Parser :: rIntegralDeclaration関数で、VivaCoreライブラリの関数の代替記録のサポートに慣れることができます。
7. static_assert
C ++ 0x標準では、新しいstatic_assertキーワードが登場しました。 構文:
static_assert(式、「エラーメッセージ」); |
テンプレート<unsigned n> struct MyStruct { static_assert(n> 5、「Nは5以上でなければなりません」); }; MyStruct <3> obj; |
エラーC2338:Nは5以上でなければなりません xx.cpp(33):クラステンプレートへの参照を参照 インスタンス化 'MyStruct <n>'がコンパイルされています と [ n = 3 ] |
8. nullptr
C ++ 0x標準以前は、C ++にはヌルポインターを示すキーワードがありませんでした。 数字0はそれを示すために使用されましたが、NULLマクロの使用は良いスタイルと見なされました。 展開すると、NULLマクロは0になり、実用的な違いはありません。 これは、Visual StudioでNULLマクロが宣言される方法です。
#define NULL 0 |
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を返します。 } |
フー(int a) フー(int a) |
例に戻り、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) |
9.新しい標準クラス
C ++ 0x標準では、名前空間stdに関連する新しい標準クラスが導入されています。 これらのクラスの多くは、Visaul Studio 2010で既にサポートされています。例は次のとおりです。
- std ::配列;
- std :: shared_ptr;
- std ::正規表現。
リストされたエンティティは通常のテンプレートクラスであるため、それらの外観は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; } //警告なし ); } |
C ++ 0xコンストラクトを使用した場合に発生する可能性のある危険なコンストラクトの検索に関して、静的アナライザーの新しいラウンドの開発が期待できます。 製品PVS-Studioは、最新のプログラムをチェックするためのツールとして位置付けられています。 現時点では、これにより64ビットおよび並列技術を意味します。 将来的には、C ++ 0xを使用した場合に予想される潜在的な問題について調査する予定です。 落とし穴が多い場合は、おそらく診断用の新しいツールの作成を開始します。
おわりに
私たちの意見では、C ++ 0xは多くの良い点をもたらします。 古いコードはすぐに近代化する必要はありませんが、リファクタリング中に時間をかけて修正することができます。 新しいコードは、新しいコンストラクトを使用してすでに記述されている場合があります。 したがって、C ++ 0xの使用を開始することは、現時点では合理的です。
書誌リスト
- Bjarne Stroustrup。 C ++ 0x-次のISO C ++標準。 http://www.viva64.com/go.php?url=304
- Visual C ++チームのブログ。 右辺値参照:VC10のC ++ 0xの機能、パート2。http://www.viva64.com/go.php?url = 305
- セルゲイ・オレンダレンコ。 C ++ 0x。 ラムダ式。 http://www.viva64.com/go.php?url=306
- マキシム。 C ++ 0xおよび初期化問題の解決。 http://www.viva64.com/go.php?url=307
- ウィキペディア C ++ 0x。 http://www.viva64.com/go.php?url=301