更新日付: 2004 年 5 月 27 日

Sun[tm] Studio 9: C++ FAQ



目次

  1. このリリースで追加された質問


  2. バージョン、パッチ、およびサポート


  3. コンパイラの互換性


  4. コーディングおよび診断


  5. ライブラリの互換性


  6. コンパイル時のパフォーマンスの向上


  7. 実行時のパフォーマンスの向上




A.このリリースで追加された質問

  1. それぞれのリリースで、どのような C++ コンパイラであるかを確認する信頼できる方法を教えてください。
  2. 最近 Solaris OS にパッチをインストールしたのですが、コードがコンパイルされなくなりました。どうしてですか?
  3. foo.cc ファイルのコンパイル、または他のソースからの参照のいずれも行っていないにもかかわらず、foo.cc に関するエラーや警告が出力されます。原因を教えてください。
  4. -P オプションを使用して、前処理によって生成された foo.i ファイルをコンパイルすると、定義が重複していることを示すエラーメッセージが返されます。原因を教えてください。
  5. C++ コンパイラのプリコンパイル済みヘッダーファイル機能を使い始めたのですが、コンパイル時のパフォーマンスが向上しません。どうしてですか?
  6. 小さなファイルに比べて、大きなファイルのコンパイルにかかる時間が長すぎます。どうしてですか?
  7. SPARC V9 アーカイブライブラリを動的ライブラリにリンクすると、エラーになります。どうしてですか?Sun Studio 8 では問題ありませんでした。
  8. 「SunWS_cache: Error: Lock attempt failed for SunWS_cache」というメッセージが返される原因を教えてください。
     
  9. 標準ライブラリストリームが、gcc や KAI ストリームより低速です。パフォーマンスの低下が大きすぎます。 解決策はあるのでしょうか ?
  10. リンカーから「ld: warning: symbol 'clog' has differing types」という警告が返されます。どうしてですか?
     
  11. -xarch=v8plus または -xarch=v8plusa を付けてコンパイルすると、STLport を使用するマルチスレッドプログラムがクラッシュします。どうしてでしょうか?
  12. abs() に対する呼び出しがあいまいであるという意味のメッセージがコンパイラから返されます。どうしてですか?
  13. 一時オブジェクトはいつ破壊されるのでしょうか?

  1. それぞれのリリースで、どのような C++ コンパイラであるかを確認する信頼できる方法を教えてください。

    どのコンパイラにも、自身の情報を提供するいくつかのマクロが事前に定義されています。コンパイラのベンダーは、リリースが変わっても、そうした事前定義マクロが安定して動作するようにする努力をしており、Sun の場合は、安定した公開インタフェースとして明記しています。

    お持ちのコンパイラを確認する方法として良いのは、事前定義マクロの有無を確認して、用途に合った文字列を出力する小さなプログラムを作成することです。疑似プログラムを作成し、-E (または、他のコンパイラの場合は相当するオプション) を付けてそのプログラムをコンパイルすることもできます。

    C++ コンパイラの事前定義マクロについては、『C++ ユーザーズガイド』の索引の「マクロ」を参照してください。具体的には、__SUNPRO_CC の値は 3 桁の 16 進数です。最初の数字がメジャーリリース、2 つ目の数字がマイナーリリース、3 つ目の数字がマイクロリリースを表します。たとえば C++ 5.6 は 0x560 です。

    以下に主な事前定義マクロの内容を示します。

     
    #ifdef __sun
           Sun compiler
    #endif
    #ifdef __SUNPRO_C
                  Sun C compiler /* __SUNPRO_C の値はバージョン番号 */
    #endif
    #ifdef __SUNPRO_CC
                   Sun C compiler /* __SUNPRO_CC の値はバージョン番号 */
    #endif
    #ifdef __sparc
           generate code for SPARC architecture
    #endif
    #ifdef __sparcv9
           generate code for 64-bit SPARC architecture
    #endif
    #ifdef __i386
           generate code for Intel architecture
    #endif

  2. 最近 Solaris OS にパッチを当てたのですが、コードがコンパイルされなくなりました。どうしてですか?

    最近のバージョンの Solaris で提供している新しい数学関数が原因で、以前は正当であったコードが不当になった可能性があります。

    以前の <math.h> の関数はすべてdouble 型専用のバージョンでした。新しい Solaris ヘッダーおよびライブラリには、float および long double 型の多重定義もあります。あいまいな呼び出しを回避するため、整数型の引数を持つ関数の呼び出しで、明示的なキャストの追加が必要になることがあります。以下に例を示します。

    #include <math.h>
    extern int x;
    double      z1 = sin(x); // 現在はあいまい
    double      z2 = sin( (double)x ); // OK
    float       z3 = sin( (float)x ); // OK
    long double z4 = sin( (long double)x ); //  OK
    

    下記の Solaris パッチは、Solaris 8 および 9 用の libm パッチで実現されている ANSI C++ に準拠した <cmath> および <math.h> ライブラリサポートを提供します。

    • Solaris 9 SPARC (パッチ 111722-04)
    • Solaris 9 i386 (パッチ 111728-03)
    • Solaris 8 SPARC (パッチ 111721-04)
    • Solaris 8 i386 (パッチ 112757-01)

  3. foo.cc ファイルのコンパイル、または他のソースからの参照のいずれも行っていないにもかかわらず、foo.cc に関するエラーや警告が出力されます。原因を教えてください。

    ヘッダーファイルの foo.h にテンプレート宣言がある場合、デフォルトではコンパイラは、C++ のファイル拡張子を持つファイル foo ((foo.cfoo.ccfoo.Cfoo.cppfoo.c++) を探し、見つかった場合は、自動的にそのファイルを取り込みます。詳細は、『C++ ユーザーズガイド』の「Template Definitions Searching」を参照してください。

    ファイル foo.cc をこのように処理したくない場合、選択肢は 2 つあります。

    • .h または .cc ファイルの名前を変更して、検索対象から排除されるようにする。
    • -template=no%extdef オプションを指定することによって、テンプレート定義ファイルの自動検索を無効にする。しかしながら、このオプションは、独立したテンプレート定義の検索もすべて無効にします。C++ 標準のライブラリ実装では、コンパイラが独立した定義を検出するものと想定しています。このため、すべてのテンプレート定義を明示的に取り込む必要があり、そうしないと定義独立モデルを使用できません。

      テンプレート定義モデルのさらに詳しい内容は、『C++ ユーザーズガイド』の 5.2.1 および 5.2.2 節を参照してください。『C++ ユーザーズガイド』の索引に、定義独立モデルおよび定義取り込みモデルを説明している場所が記載されています。

  4. -P オプションを使用して、前処理によって生成された foo.i ファイルをコンパイルすると、定義が重複していることを示すエラーメッセージが返されます。原因を教えてください。

    デフォルトでは、コンパイラは、テンプレート定義とその宣言が分離していることを黙認します。テンプレート定義モデルのさらに詳しい内容は、『C++ ユーザーズガイド』の 5.2.1 および 5.2.2 節を参照してください。『C++ ユーザーズガイド』の索引に、定義独立モデルおよび定義取り込みモデルを説明している場所が記載されています。

    必要なテンプレートの定義がない宣言が .h または .i ファイルにあると、コンパイラはテンプレート定義ファイルを探します。.h または .i ファイルと名前が同じで、拡張子が c か cc、C、c++、cpp いずれかのファイルをテンプレート定義ファイルとみなします。C++ マニュアルで説明しているように、そうしたファイルが存在すると、自動的に取り込まれます。

    foo.cc ファイルが -P を付けてコンパイルされて、foo.i が生成され、必要なテンプレートの宣言はあるが、定義がないと仮定します。コンパイラは定義ファイルを探し、foo.cc を検出します。foo.cc ファイルが自動的に取り込まれ、定義が重複することになります。

    コンパイラのテンプレート定義の検索は、-template=no%extdef オプションを指定することによって無効にできます。しかしながら、このオプションは、独立したテンプレート定義の検索もすべて無効にします。C++ 標準のライブラリ実装では、コンパイラが独立した定義を検出するものと想定しています。

    この場合は、.i ファイルをコンパイルしたいわけですから、単にファイル名を変更して、一意の名前にします。独立したテンプレートのコンパイルを無効にする必要はありません。以下に例を示します。

            CC -P foo.cc
            mv foo.i foo_prep.i
            CC -c foo_prep.i
    

    -E オプションを使用して、前処理済みファイルを生成する場合、この方法は機能しないことに注意してください。-E オプションは取り込まれたファイルの名前の記録をとるため、引き続き .cc ファイルが自動的に検出される可能性があります。

  5. C++ コンパイラのプリコンパイル済みヘッダーファイル機能を使い始めたのですが、コンパイル時のパフォーマンスが向上しません。どうしてですか?

    プリコンパイル済みヘッダーを使うことによって、コンパイル時間が短縮される保証はありません。プリコンパイル済みヘッダーを使用すると、ファイルを直接コンパイルしたときにはないオーバーヘッドが多少生じます。パフォーマンスの向上のためには、プリコンパイルで取り除ける多少の冗長性がプリコンパイル済みヘッダーに存在する必要があります。

    たとえば、プリコンパイルすることでメリットが得られる可能性が高いプログラムとしては、多数のシステムヘッダーや入出力ストリーム、STL ヘッダー、プロジェクトヘッダーを含んでいるプログラムがあります。こうしたファイルには、条件付きコンパイルコードが含まれているためです。一部のヘッダーは複数回取り込まれるため、ファイル全体の if 全体をスキャンしさえすれば、冗長なインクルードで行われることは何もないことがわかります。一般に、システムヘッダーには数百個のマクロがあります。

    プリコンパイル済みヘッダーを使用するということは、数十のファイルを開く代わりに 1 つのファイルを開くことを意味します。コメントや余分な空白と同じようにして、何もしない複数のインクルードが取り除かれます。ヘッダー内のマクロは事前に展開されます。一般に、こうしたことが、コンパイル時間の大幅な短縮につながります。

  6. 小さなファイルに比べて、大きなファイルのコンパイルにかかる時間が長すぎます。どうしてですか?

    おそらく問題はファイルのサイズではありません。考えられる原因は 3 つあります。

    • ファイル内の関数のサイズと最適化レベル

      最適化度の高い大きな関数は処理に時間がかかり、大量のメモリーを必要とすることがあります。コードで広範囲に大きなマクロが使用されている場合、小さく見える関数が、マクロ展開後に非常に大きくなることがあります。

      最適化なし (-xO? や -O? オプションを付けない) でコンパイルしてみてください。コンパイルが短時間に終了した場合、おそらく問題は、非常に大きな関数がファイルに 1 つ以上存在していて、最適化にかなりの時間とメモリーを要することにあります。

      また、コンパイルに使用したコンピュータに、コンパイルを実行するのに十分な物理メモリーがあることを確認してください。十分なメモリーがないと、最適化フェーズで時間がかかることがあります。

    • インライン関数

      C および C++ のインライン関数はマクロに似ていて、コンパイル時間に影響を与えます。関数呼び出しをインラインに展開すると、大量のコードに変換されることがあります。コンパイラは、2 つ以上の小さな関数ではなく 1 つ大きな関数に対処することになります。

      関数のインライン化を無効にすると、コンパイル時間が短縮されることがしばしばあります。当然のこととして、たいていの場合、こうして生成されるコードの実行速度は低下します。

      詳細は、『C++ ユーザーズガイド』の -xinline および「Using Inline Functions」の説明を参照してください

    • C++ のクラステンプレート

      C++ テンプレートが指定されている場合、コンパイラは呼び出されたテンプレートに基づいてコードを生成します。1 行のソースコードに対して、1 つ以上のテンプレート関数の生成が必要になることがあります。つまり、テンプレートそのものがコンパイル速度を大幅に低下させているのではなく、元のソースコードの見た目よりもコンパイラが処理しなければならないコードが多いということです。

      たとえば、以下のような関数をすでに含む標準ライブラリがなかったと仮定すると。ソースコードのこの行からは、241 個の関数が生成されることになります。

      cout << "value = " << x << endl;

       

  7. SPARC V9 アーカイブライブラリを動的ライブラリにリンクすると、エラーになります。どうしてですか?Sun Studio 8 では問題ありませんでした。

    V9 用の新しいデフォルトのコンパイラアドレスコードモデルは、-xcode=abs44 です。これは、前の -xcode=abs64 に比べてパフォーマンスを向上させます。しかしながら、この新しいコードモデルは、動的ライブラリ内では使用できません。この問題に対する解決策は 2 つあります。

    • -xcode=pic13 または -xcode=32 を付けてオブジェクトファイルを再コンパイルする。この方法を推奨します。ほぼいつでも、適切な解決策です。

    • -xcode=abs64 を付けてオブジェクトファイルを再コンパイルする。この方法では、動的ライブラリが共有できなくなります。別のメモリー領域にコピーするときに、プロセスごとにライブラリを書き換える必要があります。この方法は、パフォーマンス上の制約が大きく、システム共有率が低い条件下で非常に長時間実行するアプリケーションに対して有用です。

  8. 「SunWS_cache: Error: Lock attempt failed for SunWS_cache」というメッセージが返される原因を教えてください。

    テンプレートキャッシュに関して「lock attempt failed」というエラーメッセージが返される主な原因は 2 つあります。

    • キャッシュに対して保持していたロックが解除されない形でコンパイルが異常終了するか、強制終了させられるた。この情報は、古いバージョンのコンパイラで発生することがあります。新しいバージョンおよび最新のパッチが当てられた古いコンパイラでは、コンパイラの終了方法に関係なく必ずロックが解除されるようになっています。単にロックファイルを削除してもかまいませんが、たいていキャッシュが壊れているため、そうすると、さらに問題が起きます。最も安全な方法は、テンプレートキャッシュ全体を削除することです。

    • コンパイラプロセスがテンプレートキャッシュに書き込みを行えない。詳細は、umask(1) のマニュアルページを参照してください。具体的には、内部にキャッシュまたはファイルを作成するプロセスの umask で、同じキャッシュにアクセスする必要がある他のプロセスによるキャッシュへの書き込みを許可しておく必要があります。ディレクトリが NFS ファイルシステムにマウントされている場合は、システムを読み取り/書き込みマウントする必要があります。

  9. 標準ライブラリストリームが、gcc や KAI ストリームより低速です。パフォーマンスの低下が大きすぎます。 解決策はあるのでしょうか ?

    この問題を解決するには、リンク時に新しい C++ コンパイラオプションの -sync_stdio=no を指定するか、sync_with_stdio(false) 関数への呼び出しを追加して、再コンパイルします。

    stdlib 2.1.1 のパフォーマンス上の最大の問題は、デフォルトで C stdio と C++ ストリームの同期をとることです。cout へのあらゆる出力が、その都度ただちにフラッシュされます。プログラムが stdout ではなく cout に大量の出力を行うと、この過度のバッファーフラッシュによって、プログラムの実行時のパフォーマンスが大きな影響を受けることがあります。C++ 規格では、この動作が必要とされていますが、必ずしもすべての実装がこの仕様を満たしているわけではありません。次のプログラムは、同期をとらないと問題が発生する場合の具体例です。このプログラムは末尾に改行の付いた「Hello beautiful world」という一行を出力するものです。

        #include <iostream>
        #include <stdio.h>
        int main()
        {
            std::cout << "Hello ";
            printf("beautiful ");
            std::cout << "world";
            printf("\n");
        }
    
    しかし、cout および stdout が独立してバッファリングされている場合、このプログラムは期待された出力を行わない可能性があります。

    実行可能ファイルを再コンパイルできない場合は、リンク時に新しい C++ コンパイラオプションの -sync_stdio=no を指定してください。このオプションは、プログラム出力が発生する前のプログラム初期化時に sync_with_stdio( ) を呼び出します。

    再コンパイル可能な場合は、プログラム出力の前に sync_with_stdio(false) に対する呼び出しを追加することによって、出力の同期をとる必要がないことを指示します。以下は、呼び出し例です。

            #include <iostream>
            int main(int argc, char** argv)
            {
                    std::ios::sync_with_stdio(false);
            }
    

    sync_with_stdio に対する呼び出しは、プログラムの最初の呼び出しにします。

  10. -sync_stdio の詳細は、『C++ ユーザーズガイド』または C++ マニュアルページの CC(1) を参照してください。

  11. リンカーから「ld: warning: symbol 'clog' has differing types」という警告が返されます。どうしてですか?

    同じプログラム内で libm.so.2 と旧式の入出力ストリームライブラリがリンクされると、リンカーは、異なる種類の弱いシンボルのペアがあることを警告します。この警告は無視してかまいません。

    Solaris 10 では、デフォルトの数学ライブラリは libm.so.2 で、C99 規格で求められているように、このライブラリの大域の名前空間には、「clog」という複素数対数関数が含まれています。これに対し、-compat=4 または -library=iostream を指定することによって C++ の旧式の入出力ストリームを使用すると、大域の名前空間にバッファー付き標準出力ストリームの「clog」が使用されます。(標準入出力ストリームには、この矛盾するシンボルはありません。)

    ヘッダーおよびライブラリが調整されて、これらの「clog」シンボルはそれぞれ暗黙に名前が変更されるため、1 つのプログラムでその両方を使用できます。しかし、元のシンボルを探す古いバイナリが引き続きリンクできるようにするには、これらライブラリのそれぞれで元のシンボルのスペルを弱いシンボルとして保持する必要があります。

    このため、入出力ストリームと数学ライブラリの宣言が使用されるようにするには、これらのエンティティを自分で宣言するのではなく、適切なシステムヘッダーをインクルードします。

  12. -xarch=v8plus または -xarch=v8plusa を付けてコンパイルすると、STLport を使用するマルチスレッドプログラムがクラッシュします。どうしてでしょうか?

    元々、STLport には、-xarch=v8plus または -xarch=v8plusa を付けてコンパイルしたとき、正しいマルチスレッド動作を妨げるバグがありました。このバグは修正されていますが、この修正には STLport ヘッダーの変更と、いくつかの STLport オブジェクトの変更が含まれています。以前のバージョンの STLport ヘッダーを使ってコンパイルしたコードは、新しいプログラムにリンクする前に C++ 5.6 かパッチを当てた以前のリリースで再コンパイルする必要があります。

  13. abs() に対する呼び出しがあいまいであるという意味のメッセージがコンパイラから返されます。どうしてですか?

    C++ 規格の節 26.5 では、abs 関数を次のように多重定義することを求めています。

    • <stdlib.h> および <cstdlib>

      int  abs(int);
      long abs(long);
      

    • <math.h> および <cmath>

      float       abs(float);
      double      abs(double);
      long double abs(long double);
      

    かなり最近まで、Solaris 上で使用できる abs は従来の int 版だけでした。どのような数値型で abs を呼び出しても、値は、<stdlib.h> または <cstdlib> がインクルードされているものとして、暗黙でint 型に変換され、int 版の abs が呼び出されました。

    最近の更新によって、Solaris ヘッダーおよびライブラリは C++ の数学関数に関する 規格に準拠したものになっています。

    たとえば、<stdlib.h> ではなく、<math.h> をインクルードして、整数型の引数で abs を呼び出した場合、コンパイラは、3 つある浮動小数点型バージョンの abs 関数のうちの 1 つを選択する必要があります。整数値は任意の浮動小数点型に変更でき、他に優先する変換はありません(参考: C++ 規格 13.3.3 節)。このため、この関数呼び出しはあいまいになります。つまり、あいまいエラーは、C++ 規格に準拠しているコンパイラであれば、どれでも返されます。

    整数型の引数で abs 関数を呼び出す場合、その適切な宣言を使用されるようにするには、標準ヘッダーの <stdlib.h> または <cstdlib> をインクルードします。浮動小数点型の値で abs を呼び出す場合は、<math.h> または <cmath> もインクルードします。

    簡単なプログラミングの習慣として、<math.h><cmath> をインクルードする場合は、<stdlib.h> または <cstdlib> もインクルードすることを推奨します。

    同様のことは、cossqrt などの他の数学関数にも当てはまります。現在の Solaris のヘッダーおおびライブラリは C++ 規格に準拠しており、それら関数の float、double、および long double の多重定義バージョンを提供しています。たとえば整数型の値で sqrt を呼び出した場合、かっては 1 つのバージョンの sqrt しかなかったため、コンパイルが行われていました。しかし、現在は 3 つの浮動小数点バージョンがあるため、整数値を目的の浮動小数点型にキャストする必要があります。

                    double root_2 = sqrt(2); // エラー
            double root_2 = sqrt(2.0); // OK
                    double x = sqrt(int_value); // エラー
            double x = sqrt(double(int_value)); // OK
    

  14. 一時オブジェクトはいつ破壊されるのでしょうか?

    コンパイラが一時オブジェクトを作成する理由としては、便宜上のこともあれば、言語規則でそのことが求めてられているためのこともあります。たとえば関数が返す値は一時オブジェクトで、型変換の結果も一時オブジェクトです。

    オリジナルの C++ の規則では、一時オブジェクト ("temp") は、それが作成されたブロックが終わるまでの間にいつでも破壊することができました。そうした C++ コンパイラは、ブロックの終わり (閉じる右中括弧) で temp を破壊しました。

    数年にわたる激しい討議の後、C++ 委員会は、temp を破壊すべき位置について結論に達しました。それは、temp が作成された式全体が終わる位置です。通常、この位置は、その式が現れる文が終わる位置です。これが、C++ 規格の規則です。

    しかしながら、Sun の C++ を使用している多くのプログラムが、おそらく無意識のうちに、ブロックの終わりまで temp が残っていることに依存していると予想されるため、コンパイラのデフォルトの動作は変更されていません。すなわち、デフォルトでは、一時オブジェクトは作成されたブロックの終わりの位置で破壊されます。

    規格に準拠した動作 (temp が作成された式が完全に終わる位置でその temp を破壊する) が必要な場合は、コンパイル時オプションの -features=tmplife を使用してください。

    プログラム全体を通じてこのオプションを使用する必要はありません。モジュールで作成された一時オブジェクトは、コンパイル時にこのオプションが有効かどうかに従って、そのモジュール内の式が終わる位置またはブロックが終わる位置で破壊されます。



B. バージョン、パッチ、およびサポート

  1. 「標準」および「旧式」入出力ストリームの違いはなんですか ?また、この問題に関する参考文献を教えてください。
     
  2. どの C++ コンパイラのバージョンに互換性があるのか、見分ける方法を教えてください。
  3. Sun Studio 9 で使用できるライブラリとして「認定」される RogueWave ライブラリを教えてください。
     
  4. どのようなパッチがあって、現在のパッチでどのような問題が解決されるのかを調べるには、どうすればよいでしょうか ?
     
  5. libC.so.5 および libCrun.so.1 に対するパッチは必要でしょうか ?
     

  1. 「標準」および「旧式」入出力ストリームの違いはなんですか ?また、この問題に関する参考文献を教えてください。

    2 つのライブラリの設計と実装がまったく異なります。単純な入出力のプログラミングインタフェースは酷似しています。しかし、ユーザー独自のストリームクラスやマニピュレータの書き込みなど、複雑な動作については大きく異なります。

    本バージョンの「旧式」入出力ストリームライブラリは、C++ 3.x および 4.x に付属するバージョンと互換性があります。このリリースに付属するマニュアルのほかにも、次のような参考文献があります。

    • Steve Teale
      C++ IOStreams Handbook
      Addison-Wesley 1993

    標準入出力ストリームライブラリについては、C++ Standard で説明されています。その他にも、次のような参考文献があります。

    • Nicolai Josuttis
      The C++ Standard Library
      Addison-Wesley 1999
      (C++ 標準ライブラリ全般に関するチュートリアル)


    • Angelika Langer および Klaus Kreft
      Standard C++ IOStreams and Locales
      Addison-Wesley 1999
      (入出力ストリームとロケールに関するチュートリアル)

    単純な入出力を実装するためのソースコードは、どちらの入出力ストリームも似通っています。簡単に移行するために、規格外のヘッダー <iostream.h>、<fstream.h>、および <strstream.h> が標準の入出力ストリームに装備されています。これらのヘッダーは、旧式の入出力ストリームにあるグローバル名前空間と酷似した宣言セットを提供します。これらのヘッダーを使用すると、デフォルトで標準入出力ストリームが使用されます。旧式の入出力ストリームが使用されるようにするには、-library=iostream を付けてコンパイルおよびリンクを行います。

    たとえば、次のコードは、Sun のコンパイラを使用して旧式と標準の両方の入出力ストリームで機能します。 ただし、一部のコンパイラは使用されません。

    #include <iostream.h>
    
    class myclass {
    public:
            myclass(int i) : k(i) { }
            friend ostream& operator<<(ostream&, const myclass&);
    private:
            int k;
    };
    
    // ユーザーが記述した出力演算子
    ostream& operator<<(ostream& os, const myclass& m)
    {
        os << m.k;
        return os;
    }
    
    int main()
    {
             // cout を使用した単純入出力 (cin)
        cout << "Enter a number: " << endl;
        int val;
        if( ! (cin >> val) ) {
            cout << "Invalid entry, using zero" << endl;
            val = 0;
        }
    
             // ユーザーが記述した出力演算子を使用
        myclass m(val);
        cout << "Value is " << m << endl;
    }
    

    次のいずれかの方法で Sun コンパイラを使用して、このコードをコンパイル、実行します。

    example% CC example.cc # 標準モードで標準入出力ストリームを使用
    example% CC -library=iostream example.cc # 標準モードで旧式の入出力ストリームを使用
    example% CC -compat=4 example.cc # C++ 4.2 互換モード
    
  2. どの C++ コンパイラのバージョンに互換性があるのか、見分ける方法を教えてください。

    まず、定義しておきます。 「上方互換」とは、先行バージョンのコンパイラでコンパイルされたオブジェクトコードが後続バージョンのコンパイラでコンパイルされたコードとリンクできることを意味します。 この場合、最終リンクで最新のコンパイラを使用する必要があります。

    C++ 4.0、4.1、および 4.2 コンパイラは上方互換性があります (C++ 4.2 マニュアルに記述されているように、コンパイラのバージョン間にいくつかの「名前の符号化」の問題があります)。

    C++ のバージョン 5.0 〜 5.6 までのコンパイラは、互換モード (-compat) で、4.2 コンパイラと上方互換です。C++ 4.2 とバージョン 5.0 〜 5.6 で生成された実際のオブジェクトコードとは完全に互換性がありますが、後続コンパイラが発するデバッグ情報 (スタブ) は先行デバッガと互換性がありません。

    デフォルトの標準モードでは、C++ の 5.0 〜 5.6 までのコンパイラは、上方互換性があります。実際のオブジェクトコードは完全に互換性がありますが、後続コンパイラの発するデバッグ情報 (スタブ) は先行デバッガと互換性がありません。

  3. Sun Studio 9 で使用できるライブラリとして「認定」される RogueWave ライブラリを教えてください。

    Sun のコンパイラの各バージョン用に、どのベンダーがどの製品を保証しているのかを確実に追跡することはできません。そして、この FAQ を常に最新に維持するのも、依然として難しい状態です。C++ コンパイラの特定バージョンについてベンダーが製品テストを行なったかどうかについては、そのベンダーに問い合わせる必要があります。

    ただし、Sun のコンパイラに付属している一部の RogueWave ライブラリについては、出荷バージョンと互換性があることを暗黙に保証します。

  4. どのようなパッチがあって、現在のパッチでどのような問題が解決されるのかを調べるには、どうすればよいでしょうか ?

    製品パッチの最新情報については、開発者向けの次のポータルサイトをご確認ください。
    http://developers.sun.com/prodtech/cc

    製品のパッチは、http://sunsolve.sun.com からダウンロードできます。

  5. libC.so.5 および libCrun.so.1 に対するパッチは必要でしょうか ?

    一般に、Solaris[tm] オペレーティングシステムには、これらのライブラリの最新版が付属しています。しかし、しばしば、これらのライブラリには、バグの修正やパフォーマンスの改良のためのパッチが提供されます。そうしたパッチは常に累積されていて、常に後方互換であるため、http://sunsolve.sun.com から入手可能な最新のパッチを利用することを推奨します。次の表は、2004 年 7 月現在のパッチ ID の一覧です。

    データベースで最新のパッケージの有無を確認してください。パッケージ名は 32 ビット版が SUNWlibC、64 ビット版が SUNWlibCx です。検索範囲を「Search」リストボックスのデフォルトである「All Sun Internal Collections」 ではなく、「Patch Descriptions」に絞って、libCrun を検索してください。

    表 1: libC および libCrun のパッチ
    パッチ ID Solaris
    オペレーティングシステム
    アーキテクチャ

    108434-17

    8

    SPARC/v8

    108435-17

    8

    SPARC/v9

    108436-15

    8

    x86

    111711-11

    9

    SPARC/v8

    111712-11

    9

    SPARC/v9

    111713-08

    9

    x86



C. コンパイラの互換性

  1. 互換モード (-compat) のコードと標準モードのコードを混在させることはできますか ?
  2. C++ または C プログラムと F77、F90、または F95 プログラムを組み合わせるには、どうすればよいでしょうか ?
     

  1. 互換モード (-compat) のコードと標準モードのコードを混在させることはできますか ?

    Sun では混在を推奨しません。 「プラグイン」や動的読み込みライブラリであっても、次の理由から、同じプログラム内のコードの混在はサポートしていません。

    • クラスオブジェクトの配置が異なる。

    • 関数の呼び出し処理が異なる。

    • 「名前の符号化」処理が異なる。

    • 例外処理方法が矛盾する。

    • 2 つの入出力ストリームオブジェクトを同じファイル記述子に接続すると問題が発生する。

    プログラムの 2 つの部分 (互換モードと標準モード) が通信しないときでも、コード内で例外が送出(スロー)されると、プログラムがすぐにクラッシュする可能性があります。

    状況によっては、互換モードと標準モードのオブジェクトファイルをまとめてリンクできます。この問題については、コンパイラに付属している『C++ 移行ガイド』で詳しく説明されています。第 1 章「新旧バイナリの混在」を参照してください。このガイドは、http://docs.sun.com からオンラインで入手できます。

  2. C++ または C プログラムと F77、F90、または F95 プログラムを組み合わせるには、どうすればよいでしょうか ?

    Workshop 6 update 1 (コンパイラのバージョンは 5.2) 以降、-xlang={f90|f95|f77} オプションを使用できるようになりました。このオプションは、リンク行に必要なライブラリとそれらのライブラリの出現順を正確に割り出すようドライバに指示します。

    この -xlang オプションは、C コンパイラには使用できません。C ルーチンと Fortran ルーチンを組み合わせるには、cc でコンパイルし、Fortran リンカーでリンクする必要があります。



D. コーディングおよび診断

  1. コンパイラから標準の例外クラスについてのあいまいさが報告されるのはなぜですか ?
     
  2. C++ 5.3 で派生仮想関数のスロー指定に関してエラーが出されるのはなぜですか ?
     
  3. プログラムをリンクするとテンプレートインスタンスが失われるのはなぜですか ?インスタンスはテンプレートキャッシュ内にあるようです。
     
  4. +w2 を使用しているとき、または +w2 +d を使用していないときに、関数は展開されないという警告メッセージが表示されるのはなぜですか ?
     
  5. -ptr オプションを使って、複数のテンプレートリポジトリを使用したり、複数のプロジェクト間でリポジトリを共有したりできますか ?できない場合は、どのようにすればよいでしょうか ?
     
  6. printf(%s,NULL) がなぜセグメント例外の原因になるのですか ?
     
  7. sqrt() の呼び出し方法によって、複素数の平方根の虚部の符号が異なります。これはなぜですか ?
     
  8. クラステンプレート中のフレンド関数はインスタンス化されず、リンク時にエラーになります。 C++ 5.0 ではこのようなことはありませんでした。今回のバージョンでエラーになるのはなぜですか ?
     
  9. 入れ子になったクラスから包含するクラスのメンバーにアクセスできない、というのはなぜですか ?
     
  10. 実行時に「pure virtual function call (純粋仮想関数呼び出し)」メッセージが表示される原因は何ですか ?
     
  11. 派生クラスの仮想関数は、識別形式の異なる基底クラスの仮想関数を隠す、というのはなぜですか ?他のコンパイラの場合、このコードには何も問題がありません。
     

  1. コンパイラから標準の例外クラスについてのあいまいさが報告されるのはなぜですか ?

    Solaris の標準ヘッダー <math.h> には、標準 UNIX に必要な構造体「exception」用の宣言があります。using 宣言または using 指令を使用して C++ 標準例外クラスを大域スコープに入れると、衝突が発生します。

    // 例 1
    
    #include <math.h>
    #include <exception>
    using namespace std; // using 宣言
    exception E;  // エラー、例外があいまい
    
    // 例 2:
    
    #include <math.h>
    #include <exception>
    using std::exception; // using 指令
    exception E;  // エラー、例外に対する多重宣言
    

    using 宣言 と using 指令を比較すると、名前解決は微妙に異なります。 そのため、エラーメッセージは完全に一致しません。

    回避策:

    1. <math.h> の代わりに <cmath>を使用してください。Solaris の <cmath> には、C および C++ 規格で指定されている宣言しか含まれません。<math.h> の UNIX 固有の機能が必要である場合、この解決策は使用できません。


    2. <math.h> を使用する場合は、using std::exception; と記述しないでください。明示的に std::exception を記述するか、typedef を使用して標準例外クラスにアクセスしてください。 次に例を示します。
      #include <math.h>
      #include <exception>
      std::exception E; // OK
      typedef std::exception stdException; // OK
      stdException F; // OK
      
    3. using namespace std を記述しないでください。
      C++ の名前空間 std には非常に多くの名前が含まれるため、実際のコードでこの指令を使用すると、アプリケーションコードまたは他社製のライブラリと衝突する可能性があります (C++ プログラミングに関する書籍や記事では、この using 指令を持たせて小さなサンプルプログラムをさらに単純化していることがあります)。個々の using 宣言か、明示的な修飾名を使用します。


  2. C++ 5.3 で派生仮想関数のスロー指定に関してエラーが出されるのはなぜですか ?

    5.3 C++ コンパイラが新たに強制する C++ 規則では、派生クラスの仮想関数は、上書きされる関数が許容する例外のみを許容できます。上書き関数の制限を強化することはできますが、制限を緩めることはできません。次の例で考えてみてください。

    class Base {
    public:
    	// int 型の例外はスローできるが、それ以外はスローできない
    	virtual void f() throw(int);
    };
    class Der1 : public Base {
    public:
    virtual void f() throw(int); // ok、同じ指定
    };
    class Der2 : public Base {
    public:
    virtual void f() throw(); // ok、より厳しい制限
    };
    class Der3 : public Base {
    public:
    virtual void f() throw(int, long); // エラー、long は認められない
    };
    class Der4 : public Base {
    public:
    virtual void f() throw(char*); // エラー、char* は認められない
    };
    class Der5 : public Base {
    public:
    virtual void f(); // エラー、例外を認めている
    };
    

    次のコードは、この C++ 規則の施行理由を示しています。

    #include "base.h" // クラス Base を宣言
    void foo(Base* bp) throw()
    {
        try {
           bp->f();
        }
        catch(int) {
        }
    }
    

    Base::f() が int 例外のみをスローするよう宣言されているため、関数 foo は int 例外の捕捉は可能であり、例外のエスケープを許容しないこと宣言します。誰かが後にクラス Der5 を宣言した場合を考えてみましょう。ここでの宣言で、上書き関数は任意の例外をスローし、Der5 ポインタを foo に渡すことができます。関数 foo のコンパイル時に可視コードに問題がない場合でも、関数 foo は無効になります。

  3. プログラムをリンクするとテンプレートインスタンスが失われるのはなぜですか ?インスタンスはテンプレートキャッシュ内にあるようです。
     

    テンプレートキャッシュは、コンパイラが生成するオブジェクトファイル間の依存関係リストを保有し、テンプレートインスタンスはキャッシュに含まれています。ただし、現在のコンパイラは、-instances=extern が指定されている場合にのみテンプレートキャッシュを使用するようになっていることに注意してください。オブジェクトファイルを移動または名前変更するか、オブジェクトファイルをライブラリに結合した場合、キャッシュへの接続が失われます。2 つの代替手段を次に示します。

    • オブジェクトファイルを直接的に最終ディレクトリに生成します。テンプレートキャッシュも同じディレクトリに配置されます。

      次の例は使用しないでください。

      example% CC -c -instances=extern f1.cc
      example% mv f1.o /new/location/for/files
      

      代わりに次の例を使用してください。

      example% CC -c -instances=extern f1.cc -o /new/location/for/files/f1.o
      

      makefile マクロでプロセスをカプセル化できます。

    • CC -xar を使用すると、中間アーカイブファイル (.a) を作成できます。それぞれのアーカイブには、アーカイブ内のオブジェクトが使用するすべてのテンプレートインスタンスが含まれます。これらのアーカイブを最終プログラムにリンクします。一部のテンプレートインスタンスは異なるアーカイブで重複しますが、同一アーカイブで重複しないようにリンカーが処理します。

      example% CC -c -instances=extern f1.cc f2.cc f3.cc
      example% CC -xar f1.o f2.o f3.o -o temp1.a
      example% CC -c -instances=extern f4.cc f5.cc f6.cc
      example% CC -xar f4.o f5.0 f6.0 -o temp2.a
      example% CC -c -instances=extern main.cc
      example% CC main.o temp1.a temp2.a -o main
      
  4. +w2 を使用しているとき、または +w2 +d を使用していないときに、関数は展開されないという警告メッセージが表示されるのはなぜですか ?

    C++ コンパイラには、次の 2 種類のインライン化があります。 パーサーによって行われる C++ の inline 関数のインライン化と、コードジェネレータによって行われる最適化のインライン化です。C および Fortran コンパイラには、最適化のインライン化だけしかありません(1 つのプラットフォームでは、すべてのコンパイラに対して同じコードジェネレータが使用されます)。

    C++ コンパイラのパーサーは、暗黙にあるいは明示的に inline として宣言されたすべての関数のインライン化を展開しようと試みます。関数が大きすぎると、パーサーは、+w2 オプションを使用している場合にだけ警告を発します。+d オプションは、パーサーが関数をインライン化しないようにします。そのため、+d を使用すると警告メッセージが表示されません (また、-g オプションを指定しても、C++ インライン関数のインライン化されません)。-xO オプションは、このタイプのインライン化には何も影響を与えません。

    最適化のインライン化は、プログラム言語に左右されません。-xO4 またはそれより高い最適化レベルを選択すると、コードジェネレータは、関数がソースコード内でどのように宣言されていようとも、すべての関数を検査して、置き換える利点があれば、関数呼び出しをインラインコードで置き換えます。最適化のインライン化 (あるいは、関数のインライン化の失敗) に関するメッセージは何も表示されません。+d オプションは、最適化のインライン化には何も影響を与えません。

  5. -ptr オプションを使って、複数のテンプレートリポジトリを使用したり、複数のプロジェクト間でリポジトリを共有したりできますか ?できない場合は、どのようにすればよいでしょうか ?

    -ptr オプションは、バージョン 5.0 〜 5.6 ではサポートされていません。バージョン 4.2 で提供されていましたが、必ずしもユーザーの期待どおりに機能せず、多くの問題を発生させていました。

    最良のアドバイスは、複数のプロジェクト間でリポジトリを共有しないことです。リポジトリを共有すると、解決を図れないほど深刻な問題が発生する可能性があります。1 つのプロジェクトは 1 つのディレクトリだけでコンパイルしてください。別のプロジェクトに関係するバイナリには、別のディレクトリを使用してください。

    バージョン 5.0 以降、コンパイラは、生成するオブジェクトファイルと同じディレクトリにテンプレートリポジトリを格納します。1 つのプロジェクトに複数のリポジトリを使用する場合は、関係するリポジトリを格納するディレクトリにオブジェクトファイルを作成してください。リンク時、それらオブジェクトファイルに関係するリポジトリに対して、自動的にテンプレートインスタンスの検索が行われます。コンパイラオプションは必要ありません。

  6. printf(%s,NULL) がなぜセグメント例外の原因になるのですか ?

    アプリケーションの中には、NULL 文字ポインタは空文字列へのポインタと同じ扱いをしなければならないと誤って認識しているものがあります。これらのアプリケーションでは、NULL 文字ポインタがアクセスされるとセグメント違反が発生します。

    いくつかの理由により、 *printf() の関数ファミリには NULL ポインタのチェック機能がありません。これだけに限りませんが、次のようなことが挙げられます。

    • チェック機能を持たせると、誤った安心感を与える。プログラマに、printf() への NULL ポインタ引き渡しがうまくいったと思い込ませてしまいます。

    • プログラマが移植性のないコードを書くことを助長する。ANSI C、XPG3、XPG4、SVID2、および SVID3 では、printf("%s", pointer) には NULL で終わる文字の配列への pointer ポイントがなければならないと規定されています。

    • デバッグが難しくなる。プログラマが NULL ポインタを printf() に渡してからプログラムがコアに入るのであれば、デバッガを使用すれば、不正なポインタを指定した printf() 呼び出しを簡単に見つけ出すことができます。しかし、 printf() が "(NULL ポインタ)" と出力することにより、そのバグを非表示にした場合、その後で進行するほかのプログラムは、ある実際のデータが欲しいときに、"(NULL ポインタ)" を解釈しようとするでしょう。その時点では、実際の問題がどこに隠れているか判断することは不可能かもしれません。

    NULL ポインタを *printf に渡すアプリケーションがある場合は、ロケーション 0 に値 0 を設定するためのメカニズムを提供する特定の共有オブジェクト /usr/lib/0@0.so.1 を使用することができます。 このライブラリは、NULL ポインタの型による違いに関連するすべてのエラーをマスクするので、このライブラリは、コードを修正するまでの一時的な回避策としての使用に限定してください。

  7. sqrt() の呼び出し方法によって、複素数の平方根の虚部の符号が異なります。これはなぜですか ?

    この関数の実装は、「C99 の csqrt Annex G 仕様」で規定されています。たとえば、次のコード例の出力を見てください。

    complex sqrt (3.87267e-17, 0.632456)
    float sqrt (3.87267e-17, -0.632456)

    • 互換モードで libcomplex を使用した例:

      #include <iostream.h>
      #include <math.h>
      #include <complex.h>
       
      int main ()
      {
            complex ctemp(-0.4,0.0);
            complex c1(1.0,0.0);
            double  dtemp(-0.4);
            cout<< "complex sqrt "<< sqrt(ctemp)<<endl;
            cout<< "float sqrt   "<< sqrt(c1*dtemp)<<endl;
      }
      
    • 標準モードで libCstd を使用した例:

      #include <iostream>
      #include <math.h>
      #include <complex>
       
      using namespace std;
       
      int main ()
      {
           complex<double> ctemp(-0.4,0.0);
           complex<double> c1(1.0,0.0);
           double  dtemp(-0.4);
           cout<< "complex sqrt "<< sqrt(ctemp)<<endl;
           cout<< "float sqrt   "<< sqrt(c1*dtemp)<<endl;
      }
      
    • complex の sqrt 関数は、atan2 を使用して実装されます。次の例は、atan2 を使用することによって起こる問題を示したものです。このプログラムの出力は、以下のとおりです。

      c=-0.000000  b=-0.400000  atan2(c, b)=-3.141593
      a=0.000000  b=-0.400000  atan2(a, b)=3.141593
      

      1 つめの例では、atan2 の出力は負の数になり、2 つめの例では正の数です。これは、最初の引数として -0.0 または 0.0 のどちらが渡されるかによります。

      #include <stdio.h>
      #include <math.h>
       
      int main()
      {
          double a = 0.0;
          double b = -0.4;
          double c = a*b;
          double d = atan2(c, b);
          double e = atan2(a, b);
          printf("c=%f  b=%f  atan2(c, b)=%f\n", c, b, d);
          printf("a=%f  b=%f  atan2(a, b)=%f\n", a, b, e);
      }
      
  8. クラステンプレート中のフレンド関数はインスタンス化されず、リンク時にエラーになります。 C++ 5.0 ではこのようなことはありませんでした。今回のバージョンでエラーになるのはなぜですか ?

    次のテストケースは、C++ 5.0 コンパイラでは、エラーなしにコンパイルおよびリンクが行われますが、以降のバージョンのコンパイラでは、リンク時エラーが発生します。

    example% cat t.c
    
    #include <ostream>
    
    using std::ostream;
    
    template <class T> 
    class TList {
    public:
      friend ostream& operator<< (ostream&, const TList&);
    };
    
    template <class T>
    ostream& operator<< (ostream& os, const TList<T>& l)
    {
      return os; 
    }
    
    class OrderedEntityList {
    public:
      TList<int> *Items; 
      ostream& Print(ostream &) const;
    };
    
    ostream& 
    OrderedEntityList::Print(ostream& os) const
    {
      os << *Items;
      return os;
    }
    
    main()
    {
    }
    
    example% CC t.c
    
    Undefined			first referenced
    symbol  			    in file
    std::basic_ostream<char,std::char_traits<char> 
    >&operator<<(std::basic_ostream<char,std::char_traits<char> >&,const 
    TList<int>&) 4421826.o
    
    ld: fatal: Symbol referencing errors. No output written to a.out
    

    規格によると、このテストケースは無効です。問題点は以下の宣言にあります。

    friend ostream& operator<< (ostream&, const TList&);
    

    上の宣言は、いずれのテンプレートインスタンスも参照していません。

    非修飾名の参照は、フレンド宣言の 1 箇所で認識可能であるとしても、テンプレート宣言と一致しません。フレンド宣言をテンプレートと一致させるには、フレンド宣言をテンプレート関数として宣言するか、または名前を修飾する必要があります。

    どちらの方法でも、テンプレート用の宣言は、フレンド宣言の 1 個所で認識可能である必要があります。

    つまり、フレンド宣言はテンプレートを参照しませんが、関数呼び出しにもっとも一致する関数を宣言します (ほかの点で等しいのであれば、テンプレート関数よりもテンプレートでない関数の方が望ましいと言えます)。

    次のコードは有効です。

    template <class T> class TList; 
    // これで operator<< template を宣言できる
    
    template <class T> 
    ostream& 
    operator<< (ostream& os, const TList<T>& l) 
    { 
      return os; 
    } 
    
    template <class T> 
    class TList {
    public :
         // 関数名のスコープ修飾に注意
      friend ostream& ::operator<< (ostream&, const TList&);
    };
    
  9. Why does the compiler say a member of an enclosing class is not accessible from a nested class?
    class Outer {
        typedef int my_int;
        static int k;
        class Inner {
            my_int j;          // error、my_int にはアクセスできない
            int foo() {
                                    return k; // エラー、k にはアクセスできない
    		}
    	};
    };
    

    ARM および C++ 標準によると、入れ子になったクラスは、包含するクラスのメンバーに特別にアクセスすることはありません。my_int および kOuter 内で非公開なので、Outer のフレンドだけがメンバーにアクセスできます。入れ子になったクラスをフレンドにするためには、クラスを前もって宣言してからフレンドにする必要があります。 以下に例を示します。

    class Outer {
        typedef int my_int;
        static int k;
    															
    	// 次の 2 行をクラス定義の前に追加
        class Inner;  
        friend class Inner;
    																			
        class Inner {
            my_int j;         // OK
            int foo() {
                    return k; // OK
    		}
    	};
    };
    
  10. 実行時に「pure virtual function call (純粋仮想関数呼び出し)」メッセージが表示される原因は何ですか ?

    プログラム内に、あるエラーが発生すると必ず、「pure virtual function call (純粋仮想関数呼び出し)」メッセージが表示されます。このエラーは、次の 2 つのどちらかの場合に起こります。

    • このエラーは、抽象クラスのコンストラクタまたはデストラクタから、外部関数に「this」パラメータを渡したことによって発生します。構築および破壊時に、「this」は、コンストラクタまたはデストラクタ自身のクラスの型を持ち、最終的に構築されるクラスの型は持ちません。そこで純粋仮想関数の呼び出しを終えることができます。次の例で考えてみてください。
      class Abstract;
      
      void f(Abstract*);
      
      class Abstract {
      public:
                       virtual void m() = 0; // 純粋仮想関数
                      Abstract() { f(this); }   // コンストラクタが "this" を渡す
      };
      
      void f(Abstract* p)
      {
              p->m();
      }
      

      「Abstract」コンストラクタから f が呼び出されると、「this」は「Abstract*」型となり、関数 f は純粋仮想関数 m を呼び出そうとします。

    • 明示的な修飾子指定を行わずに定義した純粋仮想関数を呼び出そうとした場合にも、このエラーが起こることがあります。本体に純粋仮想関数を指定することはできますが、呼び出し時には修飾名を使用し、仮想呼び出しメカニズムをバイパスした場合のみ、呼び出すことができます。
      class Abstract {
      public:
                      virtual void m() = 0; // 本体は後で提供される
              void g();
      };
      
      void Abstract::m() { ... } //  m の定義 } // definition of m
      
      void Abstract::g()
      {
                      m(); // エラー、純粋な仮想 m を呼び出そうとしている
                      Abstract::m(); // OK、呼び出しが完全修飾されている
      }
      
  11. 派生クラスの仮想関数は、識別形式の異なる基底クラスの仮想関数を隠す、というのはなぜですか ?他のコンパイラの場合、このコードには何も問題がありません。

    C++ の規則では、多重定義は 1 スコープ内でのみ認められ、スコープを超えるものは認められません。基底クラスは、派生クラスのスコープを取り巻くスコープ内にあると考えられます。そのため、派生クラス内で宣言された名前は、基底クラス内のあらゆる関数を隠し、多重定義できません。この基本的な C++ 規則は、ARM よりも旧式です。

    ほかのコンパイラが、何も異常を表示しないとしても、これにより損害を被ります。 なぜなら、コードは、期待するとおりには動作しないからです。Sun のコンパイラは、そのコードを受け付けるときに警告を表示します (正当なコードですが、おそらく期待どおりには動作しません)。

    多重定義されたセットに基底クラス関数を含める場合は、基底クラス関数を現在のスコープに入れるために手を加える必要があります。デフォルトの標準モードでコンパイルを行っている場合は、宣言の使用を追加できます。

    class Base {
    public:
            virtual int    foo(int);
            virtual double foo(double);
    };
    
    class Derived : public Base {
    public:
                    using Base::foo; // 基底クラス関数を多重定義セットに追加
                    virtual double foo(double); // 基底クラスバージョンを置き換える
    };
    


E. ライブラリの互換性

  1. 完全準拠の C++ 標準ライブラリ (stdlib) の入手方法を教えてください。あるいは、現在の libCstd がサポートしていない機能には、どのようなものがありますか ?
     
  2. C++ 標準テンプレートライブラリ (STL) が必要です。どこで入手できるのでしょうか ?互換モード (-compat) 用のものはあるのでしょうか ?
     
  3. libCstd で失われた標準ライブラリ機能にはどのようなものがありますか ?
     
  4. 標準ライブラリの機能が失われることで、どのような影響があるのでしょうか ?
     
  5. 標準ストリームで機能する tools7 ライブラリのバージョンはありますか ?あるいは tools8 はまもなく入手できるようになるのでしょうか ?
     

  1. 完全準拠の C++ 標準ライブラリ (stdlib) の入手方法を教えてください。あるいは、現在の libCstd がサポートしていない機能には、どのようなものがありますか ?

    このリリースには、STLport の Standard Library 実装のバージョン 4.5.3 が、オプションの標準ライブラリとして含まれています。STLport は C++ 規格に厳密に準拠していながら、一般的な拡張機能も保持しています。ただし、デフォルトで使用される標準ライブラリとのバイナリ互換性はありません。

    現在の libCstd は、バージョン 5.0 の C++ コンパイラ用に開発されました。このバージョンは、クラスのメンバーとしてテンプレートをサポートしていません。標準ライブラリの一部には、メンバーテンプレートが必要であり、このことは一部機能が失われることを意味します。これは特に、暗黙の型変換を可能にするコンストラクタテンプレートを持つコンテナクラスにおいて起こります。その場合は、回避策としてソースコード内に明示的な型変換を記述する必要があります。

    バージョン 5.1 以降、C++ コンパイラはクラスのメンバーとしてテンプレートをサポートしていて、規格に準拠したライブラリを使用できます。ソースとバイナリレベルの互換性を損なうことなくライブラリを更新することはできないため、Sun では、同じ制限がある libCstd を引き続き出荷しています。

    gnu と SGI の Web サイトでは、パブリック版の標準ライブラリが配布されています。 また、RogueWave、Dinkumware などのベンダーからライブラリを購入することもできます。STL については、次の質問を参照してください。

  2. C++ 標準テンプレートライブラリ (STL) が必要です。どこで入手できるのでしょうか ?互換モード (-compat) 用のものはあるのでしょうか ?

    C++ コンパイラは現在、STLport の Standard Library 実装バージョン 4.5.3 をサポートしています。デフォルトのライブラリは現在も libCstd ですが、STLport 製品を代わりに使用することもできるようになりました。 このリリースには、静的アーカイブである libstlport.a と動的アーカイブである libstlport.so の両方が含まれています。

    次のコンパイラオプションを指定すると、libCstd を無効にして STLport を使用できます。

    -library=stlport4

    デフォルトの C++ 標準ライブラリ libCstd と STLport の両方が STL を含んでいます。別のバージョンの標準ライブラリを使用できますが、危険が伴い、良好な結果を保証できません。

    別の STL をプラグインするには、 -library=no%Cstd オプションを使用して、コンパイラが実際に使用するヘッダーファイルおよびライブラリを見つけられるようにします。交換用のライブラリに専用の iostreams がなく、標準の iostreams の代わりに「従来」の iostreams を使用できる場合は、コマンド行に -library=iostream を追加します。詳細な手順は、コンパイラに付属する『C++ ユーザーズガイド』の「C++ 標準ライブラリの置き換え」を参照してください。このガイドは、http://docs.sun.com からオンラインで入手できます。

  3. libCstd から無くなった標準ライブラリ機能にはどのようなものがありますか ?

    標準ライブラリは、元は (C++ 5.0 では) コンパイラ中にメンバーテンプレートおよび部分の特殊化を必要とする機能をサポートせずに構築されたものです。これらの機能は C++ 5.1 以降、使用可能ですが、標準ライブラリ内で有効にすることはできません。 これは、下方互換性を維持するためです。下記は、各機能で無効にされ、その機能性が失われた機能リストです。

    • 無効にされた機能: メンバーテンプレート関数

      • <complex> 内の complex クラス:

        template <class X> complex<T>& operator= (const complex<X>& rhs)
        template <class X> complex<T>& operator+= (const complex<X>& rhs)
        template <class X> complex<T>& operator-= (const complex<X>& rhs)
        template <class X> complex<T>& operator*= (const complex<X>& rhs)
        template <class X> complex<T>& operator/= (const complex<X>&)


      • <utility> 内の pair クラス:

        template<class U, class V> pair(const pair<U, V> &p);

      • <locale> 内の locale クラス:

        template <class Facet> locale combine(const locale& other);

      • <memory> 内の auto_Ptr クラス:

        auto_ptr(auto_ptr<Y>&);
        auto_ptr<Y>& operator =(auto_ptr<Y>&);
        template <class Y> operator auto_ptr_ref<Y>();
        template <class Y> operator auto_ptr<Y>();

      • <list> 内の list クラス:

        メンバーテンプレートのソート

      • ほとんどのテンプレートクラス:

        テンプレートコンストラクタ

    • 無効にされた機能: メンバーテンプレートクラス

      <memory> 内の auto_ptr クラス:

      template <class Y> class auto_ptr_ref{};
      auto_ptr(auto_ptr(ref<X>&);

    • 無効にされた機能: 部分的に特殊化されている関数テンプレートの引数の多重定義

      <deque>、<map>、<set>、<string>、<vector>、および <iterator> では、次のテンプレート関数 (非メンバー) はサポートされていません。

      • mapmultimap setmultisetbasic_stringvectorreverse_iterator、および istream_iterator クラスの場合:

        bool operator!= ()

      • mapmultimap setmultisetbasic_stringvector、および reverse_iterator クラスの場合:

        bool operator> ()
        bool operator>= ()
        bool operator<= ()

      • mapmultimap setmultisetbasic_string、および vector クラスの場合:

        void swap()

    • 無効にされた機能: デフォルトのパラメータを使ったテンプレートクラスの部分特殊化

    <algorithm> では、次のテンプレート関数 (非メンバー) はサポートされていません。

    count(), count_if()

    <iterator> では、次のテンプレートはサポートされません。

    template <class Iterator> struct iterator_traits {}
    template <class T> struct iterator_traits<T*> {}
    template <class T> struct iterator_traits<const T*>{}
    template typename iterator_traits::difference_type distance(InputIterator first, InputIterator last);

  4. 標準ライブラリの機能が失われることで、どのような影響があるのでしょうか ?

    C++ 規格では正当なコードがコンパイルされないことがあります。

    もっともよくあるのは、ペアの第 1 要素は const であるのに、そのようには宣言されていないマップを作成している場合です。メンバーコンストラクタテンプレートは、必要に応じ暗黙で pair<T, U>pair<const T, U> に変換します。しかし、コンストラクタがないために、変換されずに、コンパイルエラーになります。

    マップ内のペアの第 1 要素を変更することはできないため、最も簡単な解決策は、ペア型を作成するときに明示的な const を使用することです。たとえば、pair<int, T> ではなく pair<const int, T>map<int, T> ではなく map<const int, T> を使用します。

  5. 標準ストリームで機能する tools7 ライブラリのバージョンはありますか ?あるいは tools8 はまもなく入手できるようになるのでしょうか ?

    はい、あります。 ただし、C++ 5.3、5.4、5.5 および 5.6 のみです。-library=rwtools7_std コマンドを使用してこのライブラリとリンクしてください。

    RogueWave は Tools.h++ の機能を変更しており、現在では SourcePro 製品の一部としてのみ提供しています。そのような理由から、Tools.h++ version 8 は存在しません。



F. コンパイル時のパフォーマンスの向上

  1. バージョン 4.2 と比べて、バージョン 5.0 および 5.1 のコンパイラのコンパイル時間は大幅に長くなっています。将来、この問題は解決されるのでしょうか ?
     
  2. バージョン 4.2 のコンパイラと比べてバイナリのサイズがかなり大きくなります。この問題の解決策はあるのでしょうか ?
     
  3. 1 つのコンパイルプロセスを複数のプロセッサに分散できるでしょうか ?一般的に、マルチプロセッサ (MP) システムの方がコンパイル時のパフォーマンスは常に良いのでしょうか ?
     

  1. バージョン 4.2 と比べて、バージョン 5.0 および 5.1 のコンパイラのコンパイル時間は大幅に長くなっています。将来、この問題は解決されるのでしょうか ?

    5.1 のパッチ 01、およびバージョン 5.2、5.3、5.4、5.5、5.6 で、大幅にコンパイル時間を改善しました。コンパイラのパフォーマンスに満足できない場合、次の推奨事項に留意してください。

    1. 極端な場合、インライン化は膨大な時間がかかる原因になります。-xO4 または -xO5 のいずれかのオプションを使用すると、コードジェネレータがいくつかの関数を自動的にインライン化します。-xO3 のような低い最適化レベルを使用する必要があるかもしれません。 オプティマイザが特定関数を自動的にインライン化しないようにするには、-xinline オプションを使用できます。

    2. 大きな関数の明示的なインライン化を無効にします。明示的なインライン化の詳細については、以下を参照してください。

  2. バージョン 4.2 のコンパイラと比べてバイナリのサイズがかなり大きくなります。この問題の解決策はあるのでしょうか ?

    -g オプションでコンパイルを行うと、バージョン 5.0 からコンパイラがテンプレートのデバッグ用の大量の情報を書き出すため、バイナリのサイズが大きくなります。バージョン 5.1 では、多くの種類のプログラムについて、そうしたデバッグ情報のサイズは大幅に縮小されます。バージョン 5.2、5.3、5.4、5.5、5.6 では、さらに改良され、多くの場合に、バイナリサイズの縮小率は 25% から 50% 強になっています。こうした改良は、コードで名前空間やテンプレート、多数の継承レベルを持つクラス階層が使用されている場合に特に顕著です。

  3. 1 つのコンパイルプロセスを複数のプロセッサに分散できるでしょうか ?一般的に、マルチプロセッサ (MP) システムの方がコンパイル時のパフォーマンスは常に良いのでしょうか ?

    コンパイラそのものはマルチスレッド化されていません。しかし、コンパイラは 1 回のコンパイルで常に多数の他のプロセスを同時に動作させるため、MP システムの方がパフォーマンスの向上を期待できます。

    dmake (コンパイラに付属しているツールの 1 つ) を使用すると、複数のコンパイルを同時に実行できます。



G. 実行時のパフォーマンスの向上

  1. C++ は、"inline" キーワードの付いた関数を常にインライン化するのでしょうか ?あるいは、そのように記述したとしても、インライン化されない関数があるのはどうしてでしょうか ?
     

  1. C++ は、inline キーワードの付いた関数を常にインライン化するのでしょうか ?あるいは、そのように記述したとしても、インライン化されない関数があるのはどうしてでしょうか ?

    基本的に、コンパイラは、inline 宣言を指令とみなし、そのように宣言された関数をインライン化しようとします。バージョン 5.1 から 5.6 までのコンパイラでは、インライン化アルゴリズムが改良され、より多くの構文を理解するようになっています。 しかし、それでも、構文を理解できないケースが存在します。そうしたケースを次に示します。

    • バージョン 5.2 〜 5.6 の C++ コンパイラでは、ほとんど実行されることのない一部関数呼び出しは展開されません。この変更は、コンパイル速度、出力コードサイズ、および実行時速度の均衡をとる上で役立ちます。

      たとえば、静的変数の初期化で使用される式は 1 回実行されるだけであり、このためそうした式内の関数呼び出しは展開されません。インライン関数 func は、静的変数の初期化式中で呼び出されると、展開されないことがあることに注意してください。ほかの場所でインライン化されたままです。同様に、例外ハンドラ中の関数呼び出しは、これらのコードにめったに実行されないので、展開されない可能性があります。

    • 再帰関数は、最初の呼び出しレベルに対してのみインライン化されます。コンパイラは、再帰関数の呼び出しをあいまいにインライン化できません。現在の実装は、インライン化されている任意の関数への初回呼び出しの時点で停止します。  

    • 時として、小さな関数に対する呼び出しでさえもインライン化されないことがあります。この理由は、展開後の合計サイズが大きすぎるということです。たとえば、func1func2 を呼び出し、func2func3 を呼び出す、というような場合があります。これらの関数が小さくて、再帰的な呼び出しはない場合でも、コンパイラがそれらすべてを展開した場合には、展開後のサイズの合計が大きすぎる可能性があります。

      多くの標準テンプレート関数は、小さいけれども深い呼び出しチェーンを持っています。このような場合、2、3 個の呼び出しレベルだけが展開されます。

    • コンパイラは、goto 文、ループ、および try/catch 文を含む C++ インライン関数をインライン化しません。ただし、-xO4 のレベルで、オプティマイザによってインライン化されることがあります。  

    • 大きな関数はインライン化されません。C++ コンパイラのコンパイラとオプティマイザはともに、インライン化後の関数のサイズに制限があります。この制限は Sun としての一般的な推奨事項です。特別にこのサイズを緩める、または厳しくしたい場合は、内部オプションについて技術サポートに問い合わせてください。  

    • 仮想関数は、サブクラス内で再定義されていないとしても、インライン化することはできません。これは、別のコンパイルユニットにサブクラスと仮想関数の再定義が含まれているのかどうかをコンパイラが認識できないためです。  

    以前の一部のバージョンでは、複雑な if 文と return 文を持つ関数がインライン化できませんでしたが、この制限は解消されました。また、インライン関数に対するデフォルトのサイズ制限も緩和されています。プログラムによっては、こうした修正によってインライン化可能な関数が増えますが、コンパイル時間が長くなり、コードのサイズが大きくなることがあります。

    C++ インライン関数のインライン化を完全に無効にするには、+d オプションを使用します。

    これとは別に、最適化レベルが高い (-xO4) 場合、オプティマイザは、制御フローなどの結果に基づいて関数をインライン化します。このインライン化は自動的で、関数が "inline" 宣言されているかどうかに関係なく行われます。

更新日付: 2004 年 5 月 27 日

Copyright © 2004 Sun Microsystems, Inc., All rights reserved. Use is subject to license terms.