更新日付: 2003 年 3 月 1 日

Sun[tm] ONE Studio 8: C++ FAQ



目次

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


  2. コンパイラの互換性


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


  4. ライブラリの互換性


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


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




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

  1. 「標準」および「旧式」入出力ストリームの違いはなんですか ? また、この問題に関する参考文献を教えてください。
     
  2. どの C++ コンパイラに互換性があるのか、見分ける方法を教えてください。
     
  3. Sun Open Net Environment (ONE) Studio 8 で使用できるライブラリとして「認定」される 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
      (A tutorial on the entire C++ standard library)


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

    単純な入出力を実装するソースコードは、どちらの入出力ストリームも似通っています。簡単に移行するために、規格外のヘッダー <iostream.h>、<fstream.h>、および <strstream.h> が標準の入出力ストリームに装備されています。これらのヘッダーは、旧式の入出力ストリームにあるグローバルネームスペースと酷似した宣言セットを提供します。

    たとえば、次のコードは、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;}
    
  2. C++ コンパイラのバージョン間の互換性はどのようになっていますか ?

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

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

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

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

  3. Sun Open Network Environment (ONE) Studio 8 で使用できるライブラリとして「認定」される RogueWave ライブラリを教えてください。

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

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

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

    製品のパッチに関する最新情報については、ときどき Hot News の Web ページにアクセスしてみてください。
    ( http://sun.co.jp/software/sundev/suncc/hotnews.html) にアクセスしてみてください。

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

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

    コンパイラは、そのリリース日付において最新の SUNWlibC パッチとともに出荷されます。

    一般に、Solaris[tm] オペレーティング環境には、これらのライブラリの最新版が付属しています。 しかし、しばしば、これらのライブラリには、バグの修正やパフォーマンスの改良のためのパッチが提供されます。そうしたパッチは常に累積されていて、常に後方互換であるため、提供されている中でも最新のパッチを利用することを推奨します。次の表は、2002 年 3 月現在の最新のパッチ ID を示しています。

    データベースに最新のパッケージがあるか確認してください。パッケージの名前は SUNWlibC (32 ビット) または SUNWlibCx (64 ビット) です。

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

    106327-18

    7

    SPARC (R);/v8

    106300-19

    7

    SPARC/v9

    106328-17

    7

    i386

    108434-11

    8

    SPARC/v8

    108435-11

    8

    SPARC/v9

    108436-10

    8  

    i386

    111711-04

    9  

    SPARC/v8

    111712-04

    9  

    SPARC/v9

    111713-02

    9  

    i386



B. コンパイラの互換性

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

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

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

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

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

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

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

    • 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 リンカーでリンクする必要があります。



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

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

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

    Solaris では、標準ヘッダーの に、標準の 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, same specification
    };
    class Der2 : public Base {
    public:
            virtual void f() throw(); // ok, more restrictive
    };
    class Der3 : public Base {
    public:
            virtual void f() throw(int, long); // error, can't allow long
    };
    class Der4 : public Base {
    public:
            virtual void f() throw(char*); // error, can't allow char*
    };
    class Der5 : public Base {
    public:
            virtual void f(); // error, allows any exception
    };
    

    次のコードは、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. プログラムをリンクするとテンプレートインスタンスが失われるのはなぜですか ? インスタンスはテンプレートキャッシュ内にあるようです。
     

    テンプレートキャッシュは、コンパイラが生成するオブジェクトファイル間の依存関係リストを保有し、テンプレートインスタンスはキャッシュに含まれています。オブジェクトファイルを移動または名前変更するか、オブジェクトファイルをライブラリに結合した場合、キャッシュへの接続が失われます。2 つの代替手段を次に示します。

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

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

      CC -c f1.cc
      mv f1.o /new/location/for/files
      

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

      CC -c f1.cc -o /new/location/for/files/f1.o
      

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

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

      example% CC -c f1.cc f2.cc f3.cc
      example% CC -xar f1.o f2.o f3.o -o temp1.a
      example% CC -c f4.cc f5.cc f6.cc
      example% CC -xar f4.o f5.0 f6.0 -o temp2.a
      example% CC -c 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 オプションを使って、複数のテンプレートリポジトリを使用したり、複数のプロジェクト間でリポジトリを共有したりできますか ? できない場合は、どのようにすればよいでしょうか ?

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

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

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

  6. 次のようなメッセージが表示されることがあります。SunWS_cache: 情報: データベースがロックされています。待機中です...」これはどういう意味ですか ? 原因はなんですか ? どうすればこのメッセージを出さないようにできますか ?

    テンプレートを使用するプログラムをコンパイルするときに、SunWS_cache/CC_state 内のコンパイル状態情報の更新が必要な場合、コンパイラは、必ず SunWS_cache 出力ディレクトリをロックします。テンプレートを使用する複数のプロセスが同じディレクトリでコンパイルしようとする場合には、一度に 1 つのプロセスだけがロックを取得します。

    バージョン 4.2、5.0、および 5.1 の C++ コンパイラは、ロック取得待ちのプロセスがあるときは必ず、「SunWS_cache: 情報: データベースがロックされています。待機中です...」というメッセージを出力します。このメッセージは単に情報を通知するためのものですから、無視してください。このメッセージは、ある別のコンパイルプロセスがデータベースをロックしたということを意味しています。 その別のジョブが終了すると、現在のジョブが続行されます。dmake を使用しているために、このメッセージが出ると考えられます。このメッセージを出さないようにすることはできません。最新の C++ 5.2 パッチ、C++ 5.3 コンパイラ、5.4 コンパイラ、および 5.5 コンパイラでは、別のロック方式が採用されており、このメッセージは出ません。

  7. 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 ポインタの型による違いに関連するすべてのエラーをマスクするので、このライブラリは、コードを修正するまでの一時的な回避策としての使用に限定してください。

  8. 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);
      }
      
  9. クラステンプレート中のフレンド関数はインスタンス化されず、リンク時にエラーになります。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 <class T> 
    ostream& 
    operator<< (ostream& os, const TList<T>& l) 
    { 
      return os; 
    } 
    
    template <class T> 
    class TList {
    public :
      // 関数名のスコープ修飾に注意
      friend ostream& ::operator<< (ostream&, const TList&);
    };
    
  10. 入れ子になったクラスから包含するクラスのメンバーにアクセスできない、というのはなぜですか ?
    class Outer {
        typedef int my_int;
        static int k;
        class Inner {
            my_int j;          // エラー。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
    		}
    	};
    };
    
  11. 実行時に「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 の定義
      
      void Abstract::g()
      {
              m(); // エラー。純粋仮想の m を呼び出そうとしている
              Abstract::m(); // OK。呼び出しは完全に修飾されている
      }
      
  12. 派生クラスの仮想関数は、識別形式の異なる基底クラスの仮想関数を隠す、というのはなぜですか ? 他のコンパイラの場合、このコードには何も問題がありません。

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

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

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

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


D. ライブラリの互換性

  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++ コンパイラはクラスのメンバーとしてテンプレートをサポートしていて、規格に準拠したライブラリを使用できます。ソースとバイナリレベルの互換性を損なうことなくライブラリを更新することはできないため、サンでは、同じ制限がある 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 からオンラインで入手できます。

    このリリースでは、-compat モードに対応した標準ライブラリ機能は提供していません。サードパーティのライブラリ (LTLport など) を -compat モード用に構成できる場合もありますが、このリリースはそのような構成をサポートしません。

  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*> {}

  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 のみです。-library=rwtools7_std コマンドを使用してこのライブラリとリンクしてください。

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



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

  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 で大幅にコンパイル時間を改善しました。コンパイラのパフォーマンスに満足できない場合、次の推奨事項に留意してください。

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

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

    3. デフォルトでは、すべてのインスタンスがオブジェクトの SunWS_cache サブディレクトリに書き込まれます。大量のインスタンスがある場合は、リポジトリの管理に最大 95% のコンパイル時間が費やされることがあります。テンプレートリポジトリをバイパスするテンプレートインスタンス化モデル (明示的、大域、静的、および半明示的) のいずれかを使用可能かどうかを調べてください。 特定のモデルを指定するには、-instances=model オプションを使用します。 コンパイル時間の問題の原因がインスタンス化にある場合は、別のインスタンス化モデルを使用することによって、大幅にコンパイル時間が短縮されることがあります。

      これらの代替モデルでは、プログラミングスタイルが制約されることに注意してください。テンプレートインスタンス化モデルについての詳細は、『C++ ユーザーズガイド』の「テンプレートのコンパイル」を参照してください。このガイドは、http://docs.sun.com からオンラインで入手できます。

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

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

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

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

    dmake (コンパイラに付属しているツールの 1 つ) を使用すると、複数のコンパイルを同時に実行できます。ただし、この同時コンパイルでは、テンプレートキャッシュを使用する必要があるため、キャッシュへのアクセスの競合がパフォーマンスのネックになることがあります。この FAQ 集の 4c で説明されているオプションなど、キャッシュの問題を回避するためのオプションを調べてください。詳細は、コンパイラに付属する『C++ ユーザーズガイド』の「テンプレートのコンパイル」を参照してください。このガイドは、http://docs.sun.com からオンラインで入手できます。



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

  1. C++ は、"inline" キーワードの付いた関数を常にインライン化するのでしょうか ? あるいは、そのように記述したとしても、インライン化されない関数があるのは、どうしてでしょうか ?
     
  2. stdlib ストリームは、gcc または KAI ストリームより低速です。パフォーマンスの低下が大きすぎます。解決策はあるのでしょうか ?
     

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

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

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

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

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

  • stdlib ストリームは、gcc または KAI ストリームよりも低速です。パフォーマンスの低下が大きすぎます。解決策はあるのでしょうか ?

    C++ 5.4 では、デフォルトのストリームライブラリのパフォーマンスが向上しています。問題がある場合には、次の解決策を参考にしてください。

  • 更新日付: 2003 年 3 月 1 日

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