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

第 4 章 言語拡張

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

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

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


注 –

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


4.1 リンカースコープ

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

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

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

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

値 

意味 

__global

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

__symbolic

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

__hidden

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

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

__global はもっとも限定的でないスコープ、__symbolic はそれよりも限定的なスコープであり、__hidden はもっとも限定的なスコープです。

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

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

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

スレッドローカルな記憶装置を利用するには、 スレッドローカルな変数を宣言します。スレッドローカルな変数の宣言は、通常の変数宣言に宣言指定子 __thread を加えたものです。詳細は、「A.2.173 -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 型と enum 変数の前方宣言

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

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


enum E; // 不正: enum の前方宣言は認められていない
E e;    // 不正: 型 E が不完全

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


注 –

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


4.5 不完全な enum 型の使用

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


typedef enum E F; // 不正: E が不完全

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

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

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


enum E {e1, e2, e3};
int i = E::e1; // 不正: E はスコープ名ではない

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

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


注 –

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


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

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

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

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


union U {
  struct {
    int a;
    double b;
  };  // 不正: 名前のない構造体
  struct {
    char* c;
    unsigned d;
  };  // 不正: 名前のない構造体
};

これらの構造体のメンバー名は、構造体名で修飾しなくても認識されます。たとえば、共用体 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)); // 不正
}

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


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