Sun Studio 12 Update 1: C++ ユーザーズガイド

第 4 章 言語拡張

この章では、このコンパイラ特有の言語拡張について説明します。この章で扱っている機能のなかには、コマンド行でコンパイラオプションを指定しないかぎり、コンパイラが認識しないものがあります。関連するコンパイラオプションは、各セクションに適宜記載します。

-features=extensions オプションを使用すると、ほかの C++ コンパイラで一般的に認められている非標準コードをコンパイルすることができます。このオプションは、不正なコードをコンパイルする必要があり、そのコードを変更することが認められていない場合に使用することができます。

この章では、-features=extensions オプションを使用した場合にサポートされる言語拡張について説明します。


注 –

不正なコードは、どのコンパイラでも受け入れられる有効なコードに簡単に変更することができます。コードの変更が認められている場合は、このオプションを使用する代わりに、コードを有効なものに変更してください。-features=extensions オプションを使用すると、コンパイラによっては受け入れられない不正なコードが残ることになります。


4.1 リンカースコープ

次の宣言指定子を、外部シンボルの宣言や定義の制約のために使用します。静的なアーカイブやオブジェクトファイルに対して指定したスコープは、共有ライブラリや実行可能ファイルにリンクされるまで、適用されません。しかしながら、コンパイラは、与えられたリンカースコープ指定子に応じたいくつかの最適化を行うことができます。

これらの指示子を使うと、リンカースコープのマップファイルは使用しなくてすみます。 -xldscope をコマンド行で指定することによって、変数スコープのデフォルト設定を制御することもできます。

詳細は、「A.2.136 -xldscope={v}」を参照してください。

表 4–1 リンカースコープ宣言指定子

値 

意味 

__global

シンボル定義には大域リンカースコープとなります。これは、もっとも限定的でないリンカースコープです。シンボル参照はすべて、そのシンボルが定義されている最初の動的ロードモジュール内の定義と結合します。このリンカースコープは、extern シンボルの現在のリンカースコープです。

__symbolic

シンボル定義は、シンボリックリンカースコープとなります。これは、大域リンカースコープより限定的なリンカースコープです。リンク対象の動的ロードモジュール内からのシンボルへの参照はすべて、そのモジュール内に定義されているシンボルと結合します。モジュールの外側では、シンボルは大域と同じです。このリンカースコープはリンカーオプション -Bsymbolic に対応します。C++ ライブラリでは -Bsymbolic を使用できませんが、__symbolic 指定子は問題なく使用できます。リンカーの詳細については、ld(1) を参照してください。

__hidden

シンボル定義は、隠蔽リンカースコープとなります。隠蔽リンカースコープは、シンボリックリンカースコープや大域リンカースコープよりも制限されたリンカースコープです。動的ロードモジュール内の参照はすべて、そのモジュール内の定義に結合します。シンボルはモジュールの外側では認識されません。

より限定的な指定子を使ってシンボル定義を宣言しなおすことはできますが、より限定的でない指定子を使って宣言しなおすことはできません。シンボルは一度定義したら、異なる指示子で宣言することはできません。

__global はもっとも制限の少ないスコープです。__symbolic はより制限されたスコープです。__hidden はもっとも制限の多いスコープです。

仮想関数の宣言は仮想テーブルの構造と解釈に影響を及ぼすので、あらゆる仮想関数は、クラス定義を含んでいるあらゆるコンパイル単位から認識される必要があります。

C++ クラスでは、仮想テーブルや実行時型情報といった暗黙の情報の生成が必要と なることがあるため、 構造体、クラス、および共用体の宣言と定義にリンカースコープ指定子を適用できるようになっています。その場合、指定子は、構造体、クラス、または共用体キーワードの直後に置きます。こういったアプリケーションでは、すべての暗黙のメンバーに対して 1 つのリンカースコーピングが適用されます。

4.1.1 Microsoft Windows との互換性

動的ライブラリに関して Microsoft Visual C++ (MSVC++) に含まれる類似のスコープ機能との互換性を保つため、次の構文もサポートされています。

__declspec(dllexport)__symbolic と同一です。

__declspec(dllimport)__global と同一です。

Sun C++ でこの構文の利点を活用するには、-xldscope=hidden オプションを CC コマンド行に追加するべきです。結果は、MSVC++ を使用する場合と比較可能なものになります。MSVC++ を使用する場合は、定義ではなく外部シンボルの宣言のみに関して __declspec(dllimport) が使用されることが想定されます。次に例を示します。


__declspec(dllimport) int foo(); // OK 
__declspec(dllimport) int bar() { ... } // not OK  

MSVC++ は、定義に対する dllimport の許容が緩やかであり、Sun C++ を使用する場合の結果と異なります。特に、Sun C++ で定義に対して dllimport を使用する場合の結果は、シンボルがシンボリックリンケージではなくグローバルリンケージを持つことになります。Microsoft Windows 上の動的ライブラリは、シンボルのグローバルリンケージをサポートしません。この問題が発生している場合は、定義に対して dllimport ではなく dllexport を使用するようにソースコードを変更できます。その後、MSVC++ と Sun C++ で同じ結果を得ることができます。

4.2 スレッドローカルな記憶装置

スレッドローカルの変数を宣言して、スレッドローカルな記憶領域を利用します。スレッドローカルな変数の宣言は、通常の変数宣言に宣言指定子 __thread を加えたものです。詳細については、「A.2.182 -xthreadvar[= o]」を参照してください。

__thread 指定子は、スレッド変数の最初の宣言部分に含める必要があります。__thread 指定子で宣言した変数は、__thread 指定子がない場合と同じように結合されます。

__thread 指定子で宣言できるのは、静的期間を持つ変数だけです。静的期間を持つ変数とは、ファイル内で大域なもの、ファイル内で静的なもの、関数内ローカルでかつ静的なもの、クラスの静的メンバなどが含まれます。期間が動的または自動である変数を __thread 指定子を使って宣言することは避けてください。スレッド変数に静的な初期設定子を持たせることはできますが、動的な初期設定子あるいはデストラクタを持たせることはできません。たとえば、__thread int x = 4; を使用することはできますが、__thread int x = f(); を使用することはできません。スレッド変数には、特殊なコンストラクタやデストラクタを持たせるべきではありません。とくに std::string 型をスレッド変数として持たせることはできません。

スレッド変数の演算子 (&) のアドレスは、実行時に評価され、現在のスレッドの変数のアドレスが返されます。したがって、スレッド変数のアドレスは定数ではありません。

スレッド変数のアドレスは、対応するスレッドの有効期間の間は安定しています。変数の有効期間内は、プロセス内の任意のスレッドがスレッド変数のアドレスを自由に使用できます。スレッドが終了したあとは、スレッド変数のアドレスを使用できません。スレッドの変数のアドレスは、スレッドが終了するとすべて無効となります。

4.3 例外の制限の少ない仮想関数による置き換え

C++ 標準では、関数を仮想関数で置き換える場合に、置き換える側の仮想関数で、置き換えられる側の関数より制限の少ない例外を指定することはできません。置き換える側の関数の例外指定は、置き換えられる側の関数と同じか、それよりも制限されている必要があります。例外指定がないと、あらゆる例外が認められてしまうことに注意してください。

たとえば、基底クラスのポインタを使用して関数を呼び出す場合を考えてみましょう。その関数に例外指定が含まれていれば、それ以外の例外が送出されることはありません。しかし、置き換える側の関数で、それよりも制限の少ない例外指定が定義されている場合は、予期しない例外が送出される可能性があり、その結果としてプログラムが異常終了することがあります。これが、前述の規則がある理由です。

-features=extensions オプションを使用すると、限定の少ない例外指定を含んだ関数による置き換えが認められます。

4.4 enum の型と変数の前方宣言の実行

-features=extensions オプションを使用すると、コンパイラにより enum の型と変数の前方宣言が認められます。さらに、不完全な enum 型による変数宣言も認められます。不完全な enum 型は、現行のプラットフォームの int 型と同じサイズと範囲を持つと想定されます。

次の 2 つの行は、-features=extensions オプションを使用した場合にコンパイルされる不正なコードの例です。


enum E; // invalid: forward declaration of enum not allowed
E e;    // invalid: type E is incomplete

enum 定義では、ほかの enum 定義を参照できず、ほかの型の相互参照もできないため、列挙型の前方宣言は必要ありません。コードを有効なものにするには、enum を使用する前に、その定義を完全なものにしておきます。


注 –

64 ビットアーキテクチャーでは、enum のサイズを int よりも大きくしなければならない場合があります。その場合に、前方宣言と定義が同じコンパイルの中で見つかると、コンパイラエラーが発生します。実際のサイズが想定されたサイズと異なっていて、コンパイラがそのことを検出できない場合は、コードのコンパイルとリンクは行われますが、実際のプログラムが正しく動作する保証はありません。特に、8 バイト値が 4 バイト変数に格納されると、プログラムの動作が不正になる可能性があります。


4.5 不完全な enum 型の使用

-features=extensions オプションを使用した場合は、不完全な enum 型は前方宣言と見なされます。たとえば、-features=extensions オプションを使用すると、次の不正なコードのコンパイルが可能になります。


typedef enum E F; // invalid, E is incomplete

前述したように、enum 型を使用する前に、その定義を記述しておくことができます。

4.6 enum 名のスコープ修飾子としての使用

enum 宣言ではスコープを指定できないため、enum 名をスコープ修飾子として使用することはできません。たとえば、次のコードは不正です。


enum E {e1, e2, e3};
int i = E::e1; // invalid: E is not a scope name

この不正なコードをコンパイルするには、-features=extensions オプションを使用します。enum 型の名前だった場合に、-features=extensions オプションはコンパイラにスコープ修飾子を無視するよう命令します。

このコードを有効なものにするには、不正な修飾子 E:: を取り除きます。


注 –

このオプションを使用すると、プログラムのタイプミスが検出されずにコンパイルされる可能性が高くなります。


4.7 名前のない struct 宣言の使用

名前のない構造体宣言は、構造体のタグも、オブジェクト名も、typedef 名も指定されていない宣言です。C++ では、名前のない構造体は認められていません。

-features=extensions オプションを使用すると、名前のない struct 宣言を使用できるようになります。ただし、この宣言は共用体のメンバーとしてだけ使用することができます。

次は、-features=extensions オプションを使用した場合にコンパイルが可能な、名前のない不正な struct 宣言の例です。


union U {
  struct {
    int a;
    double b;
  };  // invalid: anonymous struct
  struct {
    char* c;
    unsigned d;
  };  // invalid: anonymous struct
};

これらの構造体のメンバー名は、構造体名で修飾しなくても認識されます。たとえば、共用体 U が前述のコードのように定義されているとすると、次のような記述が可能です。


U u;
u.a = 1;

名前のない構造体は、名前のない共用体と同じ制約を受けます。

コードを有効なものにするには、次のようにそれぞれの構造体に名前を付けます。


union U {
  struct {
    int a;
    double b;
  } A;
  struct {
    char* c;
    unsigned d;
  } B;
};
U u;
U.A.a = 1;

4.8 名前のないクラスインスタンスのアドレスの受け渡し

一時変数のアドレスは取得できません。たとえば、次のコードは不正です。コンストラクタ呼び出しによって作成された変数のアドレスが取得されてしまうからです。ただし、-features=extensions オプションを使用した場合は、この不正なコードもコンパイル可能になります。


class C {
  public:
    C(int);
    ...
};
void f1(C*);
int main()
{
  f1(&C(2)); // invalid
}

このコードを有効なものにするには、次のように明示的な変数を使用します。


C c(2);
f1(&c);

一時オブジェクトは、関数が終了したときに破棄されます。一時変数のアドレスを取得しないようにするのは、プログラムの作成者の責任になります。また、(f1 などで) 一時変数に格納されたデータは、その変数が破棄されたときに失われます。

4.9 静的名前空間スコープ関数のクラスフレンドとしての宣言

次のコードは不正です。


class A {
  friend static void foo(<args>);
  ...
};

クラス名に外部リンケージが含まれており、また、すべての定義が同一でなければならないため、フレンド関数にも外部リンケージが含まれている必要があります。しかし、-features=extensions オプションを使用すると、このコードもコンパイルできるようになります。

おそらく、この不正なコードの目的は、クラス A の実装ファイルに、メンバーではない「ヘルパー」関数を組み込むことでしょう。そうであれば、foo を静的メンバー関数にしても結果は同じです。クライアントから呼び出せないように、この関数を非公開にすることもできます。


注 –

この拡張機能を使用すると、作成したクラスを任意のクライアントが「横取り」できるようになります。そのためには、任意のクライアントにこのクラスのヘッダーを組み込み、独自の静的関数 foo を定義します。この関数は、自動的にこのクラスのフレンド関数になります。その結果は、このクラスのメンバーをすべて公開にした場合と同じになります。


4.10 事前定義済み __func__ シンボルの関数名としての使用

-features=extensions オプションを使用すると、それぞれの関数で __func__ 識別子が const char 型の静的配列として暗黙的に宣言されます。プログラムの中で、この識別子が使用されていると、コンパイラによって次の定義が追加されます。ここで、function-name は関数の単純名です。この名前には、クラスメンバーシップ、名前空間、多重定義の情報は反映されません。


static const char __func__[] = "function-name";

たとえば、次のコードを考えてみましょう。


#include <stdio.h>
void myfunc(void)
{
  printf("%s\n", __func__);
}

この関数が呼び出されるたびに、標準出力ストリームに次の情報が出力されます。


myfunc

4.11 __packed__ 属性

struct または union の型定義に添付されるこの属性は、必要なメモリーを最小限に抑えるために、構造体または共用体の各メンバー (幅が 0 のビットフィールドを除く) の配置を指定します。enum 定義に添付する場合は、この属性は最小の整数型を使用することを示します。

struct および union 型に対してこの属性を指定する場合は、構造体または共用体の各メンバーに対して packed 属性を指定する場合と同じことを意味します。

次の例では、struct my_packed_struct のメンバーは互いに隣接してパックされますが、メンバーの内部レイアウトはパックされません。内部レイアウトをパックするには、struct my_unpacked_struct もパックする必要があります。


struct my_unpacked_struct
{
   char c;
   int i;
;
              
struct __attribute__ ((__packed__)) my_packed_struct
{
   char c;
   int  i;
   struct my_unpacked_struct s;
};

この属性を指定できるのは、enum struct、または union の定義のみです。列挙型、構造体、共用体のいずれも定義しない typedef に対して、この属性は指定できません。