より厳密な C++ コンパイラ(Clang 拡張 C++ コンパイラ)

提供: Appmethod Topics
移動先: 案内検索

Clang 拡張 C++ コンパイラと旧世代の C++ コンパイラの違い への移動


Clang ベースの C++ コンパイラは Clang をベースにしています。Clang ベースの C++ コンパイラは C++11 をサポートしていると同時に、BCC32 よりも厳密なコンパイラでもあります。

構造の構文

BCC32 では、__trycatch を混在させることができます。

Clang ベースの C++ コンパイラでは、try/catch(標準 C++)か __try/__except/__finally(Microsoft による拡張)のどちらか一方でなければなりません。

型変換

リテラル C 文字列を char * に代入すると、そのポインタが const と宣言されていない限り、警告が発生します。

BCC32 では、char *unsigned char* の間での代入が可能で警告も発生しませんが、Clang ベースの C++ コンパイラでは、これは単純型の不一致エラーになります。

int から(タイプ セーフでない標準的な)enum への暗黙の変換は、BCC32 では警告が発生しますが、可能です。Clang ベースの C++ コンパイラでは、暗黙の変換は使用できず、キャストが必要です。よくあるのは、次のように、TColorTCursor などの列挙型を使用する場合です。

TColor stones = (TColor) 0;  // ランダムな色にデフォルト設定するのではなく、黒で塗る


静的メンバの定義

C++ の静的データ メンバは、アプリケーションの 1 箇所にまとめて定義する必要があります。ヘッダー内のクラス宣言で宣言したあと、(ヘッダー ファイルではなく)ソース ファイルで定義しなければなりません。

以下に例を示します。

foo.h:
struct X { static int static_member; };

foo.cpp:
#include "foo.h"
int X::me;

bar.cpp:
#include "foo.h"
int some_function() { return X::me; }

この例では、foo.cpp に X::me の定義があり、bar.cpp でそれを使用しています。静的メンバは foo.h で宣言されていますが、foo.cpp にのみ定義されています。

32 ビット ツールでは、C++ 標準には違反していますが、X::me の多重定義が可能でした。したがって、以下のようなことも行えました。

foo.h:
struct X { static int static_member; };
int X::me;

上記の例(Clang ベースの C++ コンパイラでは誤りですが、BCC32 では許されます)では、静的データ メンバをヘッダーで定義しており、その結果、X::me がヘッダーのインクルード箇所ごとに 1 回ずつ何度も定義されることになります。Clang ベースのツールではこれは許されず、たとえば以下のようなリンカ エラーが発生するおそれがあります。

[ilink64 エラー] エラー: パブリック シンボル 'triplet::sum' がモジュール C:\USERS\WIN764\NODES.O と C:\USERS\WIN764\UNIT1.O の両方に定義されています

静的データ メンバについて、このようなシンボルの重複定義に関するリンカ エラーが発生した場合は、上記の誤ったパターンを探して、静的データ メンバの "唯一定義" 要件を満たすようにコードを修正しなければなりません。

テンプレートにおける 2 段階の名前ルックアップ

テンプレートにおける名前解決は、定義とインスタンス化という 2 つの異なる時点で試みられます。標準にあまり準拠していないコンパイラでは、解決をインスタンス化まで先送りする傾向があり、時には、標準に厳密に準拠していないコードでも正常にコンパイラできることがあります。BCC32 では、テンプレートはインスタンス化されない限りコンパイルされないため、明らかなエラーが含まれているテンプレート定義でも、使用されない限りコンパイルされます。Clang ベースの C++ コンパイラでは、このようなエラーは通知されます。

2 段階の名前ルックアップでは、LLVM プロジェクトのこのブログ エントリで詳しく解説されているように、基底クラスをチェックしないといったわずかな影響が現れます。なお、この問題に限って言えば、基底クラスにあるべき識別子を this-> で修飾するという解決策があります。

修飾された依存型には typename キーワードが必要

次のテンプレートが何を行っているかを考えてみましょう。

template <class T>
void pointerOrMultiply() {
  T::member *var;
}

このテンプレートは、テンプレート型のメンバを指すポインタを作成することを意図しているかもしれませんが、テンプレート パラメータがいくつかある場合は、乗算(結果は破棄)になる可能性もあります。テンプレートがインスタンス化されるまで解決できないようなあいまいさを避けるために、標準では、テンプレート パラメータに左右される修飾型については、以下のように先頭に typename キーワードを付けることになっており、たとえ、その解釈しかインスタンス化の際に意味を成さないと思われる場合でも、このキーワードが必要になります。

template <class T>
void definitelyAPointer() {
  typename T::member *var;
}

トークン連結

Clang ベースの C++ コンパイラでは、プリプロセッサのトークン連結演算(## 演算子によるもの)の結果は単一の有効なトークンでなければならないという規則が適用されます。

多少不自然ですが、例を挙げましょう。名前をそのまま使用するか名前空間で修飾するかを条件分けする、関数に似たマクロが、以下のようにコードベースで定義されているとしましょう。

#ifdef KEEP_IT_SIMPLE
  #define qual(C,M) M
#else
  #define qual(C,M) C##::##M
#endif

// ...

void notRecommendedJustAnExample(int counter) {
  qual(std, cout) << counter;
}

後者の場合、BCC32 ではコンパイルされ、"期待どおりに動作" します。しかし、実際には、これは正式にはサポートされていません。Clang ベースの C++ コンパイラでは以下のようなエラーが出力されます。

連結の結果が 'std::' になりましたが、これは無効なプリプロセッサ トークンです
連結の結果が '::cout' になりましたが、これは無効なプリプロセッサ トークンです

## が使用されている 2 か所でエラーになります (メモ: 1 回のマクロ呼び出しにおける複数のトークン連結演算の順序は定義されていません)。どちらも最終的に単一のトークンにはなりません。両者とも識別子と区切り記号(スコープ解決演算子)を結合しようと試みます。

このような場合の修正は簡単です。つまり、トークン連結は実際には必要ありません。識別子と演算子は、いずれにせよ最終的には隣り合わせになるのです。連結は、新しいトークンを作成する場合にのみ必要になります(そのトークンがそのあと、さらにマクロ展開の対象になります)。したがって、マクロは以下のように定義できます。

  #define qual(C,M) C::M

関連項目