より厳密な C++ コンパイラ(Clang 拡張 C++ コンパイラ)
Clang 拡張 C++ コンパイラと旧世代の C++ コンパイラの違い への移動
Clang 拡張 C++ コンパイラは Clang をベースにしています。 Clang 拡張 C++ コンパイラは C++11 をサポートしていると同時に、BCC32 よりも厳密なコンパイラでもあります。
目次
構造の構文
BCC32 では、__try
と catch
を混在させることができます。
Clang 拡張 C++ コンパイラでは、try/catch
(標準 C++)か __try/__except/__finally
(Microsoft による拡張)のどちらか一方でなければなりません。
型変換
リテラル C 文字列を char *
に代入すると、そのポインタが const
と宣言されていない限り、警告が発生します。
BCC32 では、char *
と unsigned char*
の間での代入が可能で警告も発生しませんが、Clang 拡張 C++ コンパイラでは、これは単純型の不一致エラーになります。
int
から(タイプ セーフでない標準的な)enum
への暗黙の変換は、BCC32 では警告が発生しますが、可能です。 Clang 拡張 C++ コンパイラでは、暗黙の変換は使用できず、キャストが必要です。 よくあるのは、次のように、TColor
や TCursor
などの列挙型を使用する場合です。
TColor stones = (TColor) 0; // instead of defaulting to random color, paint it black
- メモ: Vcl.Graphics.hpp では TColor clBlack が定義されています。
静的メンバの定義
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 Error] Error: Public symbol 'triplet::sum' defined in both module C:\USERS\WIN764\NODES.O and 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++ コンパイラでは以下のようなエラーが出力されます。
pasting formed 'std::', an invalid preprocessing token pasting formed '::cout', an invalid preprocessing token
##
が使用されている 2 か所でエラーになります (メモ: 1 回のマクロ呼び出しにおける複数のトークン連結演算の順序は定義されていません)。どちらも最終的に単一のトークンにはなりません。両者とも識別子と区切り記号(スコープ解決演算子)を結合しようと試みます。
このような場合の修正は簡単です。つまり、トークン連結は実際には必要ありません。 識別子と演算子は、いずれにせよ最終的には隣り合わせになるのです。 連結は、新しいトークンを作成する場合にのみ必要になります(そのトークンがそのあと、さらにマクロ展開の対象になります)。 したがって、マクロは以下のように定義できます。
#define qual(C,M) C::M