C++ 移行ガイド ホーム目次前ページへ次ページへ索引


第 1 章

概要

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


注 - C++ 5.0 コンパイラまたは Sun WorkShop 6 C++ コンパイラの標準モード (デフォルトのモード) でコンパイルしたオブジェクトコードと、それ以前のバージョンの C++ コンパイラでコンパイルした C++ コードとの間には互換性はありません。ただし、古いオブジェクトコードでも、そのライブラリが他への依存関係を持たない場合は C++ 5.0 コンパイラおよび Sun WorkShop 6 C++ コンパイラの両方で使用できます。詳細は、「バイナリ互換の問題」を参照してください。

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++ によって標準が規定されています。標準モードの Sun WorkShop 6 C++ コンパイラでは、ほぼすべての言語仕様が、この国際標準の規定どおりに実装されています。現在のリリースに付属している README (最新情報) ファイルには、この標準と異なる仕様の説明が含まれています。

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

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

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

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

コンパイラの動作モード

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

標準モード

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

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

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

互換モード

コンパイラには、C++ 4 から標準モードへの移行のために互換モードが用意されています。互換モードでは、C++ 4 コンパイラと、バイナリレベルでは完全な互換性が、ソースレベルではほぼ完全な互換性が保たれます (「互換性」とは、「上位互換性」を意味します。古いソースコードおよびバイナリコードは、新しいコンパイラで動作できますが、この逆に、新しいコンパイラ用に作成されたコードが古いコンパイラで動作するとは限りません)。互換モードと標準モードの間にはバイナリレベルの互換性はありません。互換モードは、IA と SPARC のプラットフォーム上で動作している Solaris 2.6、Solaris 7、および Solaris 8 オペレーティング環境で使用できます。しかし、SPARC V9 (64 ビット) プロセッサでは使用できません。つまり、新しいコンパイラでコンパイルしたコードと別のバージョンのコンパイラでコンパイルしたコードはリンクできないという制限があります。

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

バイナリ互換の問題

「アプリケーションバイナリインタフェース」 (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 を設計しました。この ABI は、C++ 5.0 コンパイラおよび Sun WorkShop 6 C++ コンパイラでデフォルトとして使用されています。

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

新旧バイナリの混在

4.2 コンパイラでコンパイルしたオブジェクトファイルやライブラリは、Sun WorkShop 6 C++ コンパイラでコンパイルしたオブジェクトファイルやライブラリとは絶対にリンクできないというわけではありません。リンクできないのは、ファイルやライブラリが 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 のいずれかに代入されます。

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

e ? a : (b = c)

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

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

関数ポインタと void*

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

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

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

4.2 コンパイラでは、上記のコード例でコメントを付けた行は f(void*) を呼び出します。現在では一致する関数がないため、+w2 オプションを使用するとエラーメッセージが出されます。f((void*)g) のような明示的なキャストを追加することもできますが、コードが無効なため警告メッセージが出されます。関数ポインタと void* 間の変換は、すべてのバージョンの Solaris オペレーティング環境上で有効です。しかし、すべてのプラットフォームに移植可能というわけではありません。

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

将来の変更について

現在のコンパイラが C++ 標準に準拠していない場合もあります (たとえば、同じエントリを参照する宣言に関する問題)。このような場合、プログラムは正しくリンクされない可能性があります。この問題を回避するには、以下の規則に従ってください。後のリリースでこの問題が修正されても、名前は同じように符号化されます。

この符号化に関する問題の影響をどうしても避けられない場合 (たとえば、自分が所有していないヘッダーやライブラリでこの問題が発生する場合) は、次の例のように弱いシンボルを使用することで、宣言と定義を一致させることができます。

extern "C" void __1c_missing_symbol();
#pragma weak __1c_missing_symbol = __1c_existing_symbol

このような宣言では、必ず符号化名を使用してください。


サン・マイクロシステムズ株式会社
Copyright information. All rights reserved.
ホーム   |   目次   |   前ページへ   |   次ページへ   |   索引