C++ 移行ガイド

第 1 章 概要

このマニュアルでは、C++ 4.0、4.01、4.1、4.2 をまとめて「C++ 4」と呼びます。C++ 4 でコンパイルし、動作していた C++ ソースコードは、C++ 言語の定義の変更に起因するいくつかの例外はありますが、そのまま C++ 5.0 でも機能します。C++ 5.0 コンパイラには、ほぼすべての C++ 4 コードを変更なしで使用できる互換モード (-compat=4) が用意されています。


注 -

標準モードでコンパイルした C++ 5.0 オブジェクトコードと、それ以前のバージョンの C++ コンパイラでコンパイルした C++ コードとの間には、互換性はありません。ただし、古いオブジェクトコードで構成されていて他に依存しないライブラリは、C++ 5.0 コンパイラでも利用できます。詳細は、「バイナリ互換の問題」を参照してください。


C++言語

C++ は、Bjarne Stroustrup 著『C++ Programming Language』(1986 年刊) で初めて登場し、その後、より公式の解説書として、Margaret Elis、Bjarne Stroustrup 共著『注解 C++リファレンス・マニュアル』(通称 ARM、トッパン刊) が刊行されました。Sun C++ 4 コンパイラは、主としてその ARM の定義に基づき、それに、当時登場しつつあった C++標準の一部の仕様が追加されたものです。C++ 4、特に C++ 4.2 コンパイラに追加された仕様の大部分は、ソースレベルおよびバイナリレベルの互換性の問題を起こさないものに限られていました。

現在 C++ は、国際標準の 1 つ、ISO/IEC 14882:1998 Programming Languages - C++ によって標準が規定されています。標準モードでは、ほぼすべての言語仕様が、この国際標準の規定どおりに実装されています。現在のリリースに付属している README (最新情報) ファイルには、この標準と異なる仕様の説明が含まれています。

C++ 言語の定義は変更されています。このため、古いソースコードをそのままではコンパイルできないことがあります。この最も顕著な例は、C++の標準ライブラリ全体が名前空間の std に定義されるようになったという点です。今や、標準のヘッダー名は.h なしの <iostream> であり、名前の cout および endl は大域名前空間ではなく名前空間 std に存在するため、標準に厳密に準拠したコンパイラでは従来の C++プログラムをコンパイルすることはできません。


#include <iostream.h>
int main() { cout <<“Hello, world!”<< endl; }

C++ 5.0 コンパイラには拡張機能として、標準モードでも従来の C++プログラムがコンパイルできるように <iostream.h> ヘッダーが用意されています。言語の変更は、ソースコードの修正を必要とするばかりでなく、バイナリレベルの互換性の問題を起こします。そのため、バージョン 5.0 より前の Sun C ++コンパイラには、C++ 標準に準拠するための変更は行われていません。

新しい C ++言語機能には、プログラムのバイナリ表現の変更を伴うものあります。この問題については、「バイナリ互換の問題」でさらに説明します。

コンパイラの動作モード

C++ 5.0 コンパイラには、「標準」モードと「互換」モードの 2 つの動作モードがあります。

標準モード

標準モードでは、C++ 国際標準の大部分の仕様が実装されており、C++ 4 で受け入れられていた言語とソースレベルの互換性の問題がいくつかあります。

さらに重要なことに、C++ 5.0 コンパイラは、標準モードでは、C++ 4.2 とは異なるアプリケーションバイナリインタフェース (ABI) を使用します。このため、一般的に、標準モードで生成されたコードと 4.2 コンパイラで生成されたコードとの間には互換性がなく、リンクすることはできません。この問題については、「バイナリ互換の問題」でさらに説明します。

既存のコードを 5.0 の標準モードでコンパイルするには、いくつかの修正が必要です。修正が必要な理由としては、以下が挙げられます。

互換モード

C++ 5.0 コンパイラには、C++ 4.2 から C++ 5.0 標準モードへの移行のために互換モードが用意されています。互換モードでは、C++ 4.2 コンパイラと、バイナリレベルでは完全な互換性が、ソースレベルではほぼ完全な互換性が保たれます (「互換性」とは、「上位互換性」を意味します)。古いソースコードおよびバイナリコードは、新しいコンパイラで動作できますが、この逆に、新しいコンパイラ用に作成されたコードが古いコンパイラで動作するとは限りません) 。ただし、互換モードと標準モードとの間にバイナリレベルの互換性はありません。互換モードは、Solaris 2.5.1、2.6、7 のいずれかを実行する Intel および SPARC で使用できます。ただし、SPARC v9 (64 ビット対応の Solaris 7) で使用することはできません。

互換モードを使用する理由としては、以下が挙げられます。

バイナリ互換の問題

「アプリケーションバイナリインタフェース」 (ABI) には、コンパイラによって生成されるオブジェクトプログラムのマシンレベルの特性が定義されています。このマシンレベルの特性とは、基本型のサイズや境界整列条件、構造体型または集合体型の配置、関数の呼び出し方法、プログラムに定義されている構成要素の実名、その他多数の機能を指します。Solaris 用 C++ ABI の大部分は、C 言語用の ABI である基本 Solaris ABI と同じです。

言語の変更

C++ には、C 言語用 ABI にはない多数の機能 (クラスメンバー関数、多重定義関数と演算子、型保証リンケージ、例外、テンプレートなど) が導入されています。C++ では、主要なバージョンが出るたびに、それまでの ABI では実装することが不可能な言語機能が追加されています。そのため、クラスオブジェクトの配置方法、一部関数の呼び出し方法、型保証リンケージ (「名前の符号化」) の実装方法などの変更が ABI に必要になりました。

C++ 4.0 コンパイラには、ARM に定義されている言語仕様が実装されています。その後、C++ 4.2 コンパイラの発表までの間に、C++ 委員会によって、一部の ABI の変更を必要とする、多数の新しい言語機能が導入されました。その後の言語に対する機能の追加あるいは変更に伴って、さらに ABI の変更が必要になることは確実なため、C++ の 4.2 時点では、サンは、ABI に対する変更を必要としないものだけを新機能として実装する道を選びました。これは、バージョンの異なるコンパイラで、コンパイルした複数のバイナリファイルを、ユーザーが維持管理するために必要な作業をできるかぎり抑えることを意図したものでした。今回のリリースに先立ってC++ 標準が制定されたため、サンは、完全な C++ 言語の実装を可能にする新しい ABI を設計しました。C++ 5.0 コンパイラは、デフォルトでは、この新しい ABI を使用します。

ABI に影響する言語の変更としては、たとえば、newdelete 未使用領域関数の名前、識別形式、意味の変更があります。識別形式が同じあっても、テンプレート関数と非テープレート関数は、異なる関数であるという新しい規則も、ABI に影響する言語の変更の 1 つです。そのため「名前の符号化」の変更が必要となり、古いコンパイル済みコードとの非互換性の問題が生まれました。bool 型が導入されたことでも、特に標準ライブラリのインタフェースという点で ABI の変更が必要になりました。これらの変更のため、不必要に非効率的な実行時コードの原因となっていた古い ABI のいろいろな部分が改善されています。

新旧バイナリの混在

4.2 コンパイラでコンパイルしたオブジェクトファイルやライブラリは、5.0 コンパイラでコンパイルしたオブジェクトファイルやライブラリとは絶対にリンクできないというわけではありません。リンクできないのは、ファイルやライブラリが C++ インタフェースを持っている場合です。

C++ でコーディングされているにもかかわらず、外部に対して C インタフェースしか用意されていないライブラリがときどきあります。C インタフェースを持っているということは、インターフェース先は元のプログラムが C++ で作成されていることを知らないということです。もっと具体的に言えば、C インタフェースを持つということは、以下のことがすべて当てはまることを意味します。

ライブラリが C インタフェースの条件を満たす場合、そのライブラリは、C ライブラリを使用可能なあらゆる場所で使用できます。つまり、そうしたライブラリのコンパイルと、そのライブラリとリンクするオブジェクトファイルのコンパイルには、異なるバージョンの C++ コンパイラを使用することができます。

ただし、上記の条件の 1 つでも満たされない場合は、ファイルとライブラリをリンクすることはできません。リンクが成功したとしても、プログラムは正しく動作しません。

条件式

C++ 標準では、条件式に関する規則に 1 つの変更が加えられています。この違いは、以下のような式にのみ影響を与えます。

e ? a : b = c

問題になるのは、グループ化のための括弧がない状態で、コロンの後で代入が行われる場合です。

4.2 コンパイラでは、従来の C++ の規則が使用されており、上記の式は、以下のように書かれているかのように扱われました。

(e ? a : b) = c

つまり、c の値は、e の値に従って a または b のいずれかに代入されます。

5.0 コンパイラでは、互換モードと標準モードのどちらでも、新しい C++ の規則が使用され、上記の式は以下のように書かれているかのように扱われます。

e ? a : (b = c)

つまり、ce が偽の場合にだけ b に代入されます。

解決策: 必ず括弧を使用して、自分の意図を正確に指示してください。括弧を使用すると、どのコンパイラでコンパイルしたときでも、コードは同じ意味を持つようになります。

関数ポインタと void*

C では、「関数へのポインタ」と void* の間の暗黙的な変換は行われません。ARM で、「値が入れば」関数ポインタと void* の間で暗黙的な変換をする機能が追加され、C++ 4.2 でこの規則が実装されました。しかし、この暗黙的な変換は、予測できない関数の多重定義動作の原因となり、コードの移植性を損うため、その後 C++ から削除されました。さらに現在では、関数へのポインタと void* の間の暗黙的な変換も、キャストの場合を含め行われません。

C++ 5.0 は、互換モードでも標準モードでも、関数へのポインタと void* の間で暗黙的または明示的な変換が行われると警告を出します。C++ 5.0 は、どちらのモードでも、多重定義された関数呼び出しを解決するときにそのような暗黙的な変換を行いません。4.2 コンパイラに準拠しているそのようなコードは、5.0 コンパイラではエラー (一致する関数がない) になります。多重定義を適切に解決するために暗黙的な変換が必要な場合は、キャストを追加する必要があります。たとえば、次のようにします。


int g(int);
typedef void (*fptr)();
int f(void*);
int f(fptr);
void foo()
{
    f(g);          // この行は異なる動作になる
}

4.2 コンパイラでは、上記のコード例でコメントを付けた行は f(void*) を呼び出します。一方 5.0 コンパイラでは、一致する関数がないため、エラーメッセージが出されます。f((void*)g) のような明示的なキャストを追加することもできますが、コードが無効なため警告メッセージが出されます。コードは、関数ポインタと void* の間の変換が必要ないように修正すべきです。このようなコードは、変換が行われていたときでも移植性がありませんでした。

C++ には、void* に対応する「汎用関数ポインタ」はありません。C++ のすべての関数ポインタのサイズと表現は、サポートされるどのプラットフォームでも同じです。したがって、都合のよい任意の関数ポインタ型を使って関数ポインタの値を保持できます。この解決策は、多くのプラットフォームに対して移植性があります。従来と同じように、ポインタ値を使って関数を呼び出す場合は、ポインタ値を元の型に変換する必要があります。extern“C” 関数へのポインタ」を参照してください。