C++とJavaの詳細
公開: 2022-07-22数え切れないほどの記事がC++とJavaの技術的機能を比較していますが、どの違いを考慮することが最も重要ですか? たとえば、Javaが多重継承をサポートしておらず、C ++がサポートしていることを比較すると、それはどういう意味ですか? そして、それは良いことですか? これがJavaの利点であると主張する人もいれば、問題であると宣言する人もいます。
開発者がC++、Java、または別の言語を完全に選択する必要がある状況、さらに重要なことに、決定が重要である理由を調べてみましょう。
基本を調べる:言語の構築とエコシステム
TypeScriptがJavaScriptにコンパイルされるのと同様に、C++はCコンパイラのフロントエンドとして1985年にリリースされました。 最新のC++コンパイラは通常、ネイティブマシンコードにコンパイルされます。 C ++のコンパイラは移植性を低下させ、新しいターゲットアーキテクチャの再構築が必要になると主張する人もいますが、C++コードはほぼすべてのプロセッサプラットフォームで実行されます。
1995年に最初にリリースされたJavaは、ネイティブコードに直接ビルドされません。 代わりに、Javaは、Java仮想マシン(JVM)で実行される中間バイナリ表現であるバイトコードを構築します。 つまり、Javaコンパイラの出力を実行するには、プラットフォーム固有のネイティブ実行可能ファイルが必要です。
C ++とJavaはどちらも、構文がCに似ているため、Cに似た言語のファミリーに分類されます。 最も重要な違いはエコシステムです。C++はCまたはC++、またはオペレーティングシステムのAPIに基づいてライブラリをシームレスに呼び出すことができますが、JavaはJavaベースのライブラリに最適です。 Java Native Interface(JNI)APIを使用してJavaのCライブラリにアクセスできますが、エラーが発生しやすく、CまたはC++コードが必要です。 また、C ++は低水準言語であるため、C++はJavaよりも簡単にハードウェアと対話します。
詳細なトレードオフ:ジェネリック、メモリなど
多くの観点からC++とJavaを比較できます。 場合によっては、C++とJavaのどちらを選択するかが明確になります。 ネイティブAndroidアプリケーションは、アプリがゲームでない限り、通常Javaを使用する必要があります。 ほとんどのゲーム開発者は、可能な限りスムーズなリアルタイムアニメーションを実現するために、C++または別の言語を選択する必要があります。 Javaのメモリ管理は、ゲームプレイ中にラグを引き起こすことがよくあります。
ゲームではないクロスプラットフォームアプリケーションは、この説明の範囲を超えています。 この場合、C ++もJavaも、効率的なGUI開発には冗長すぎるため、理想的ではありません。 高性能アプリの場合、手間のかかる作業を行うためにC ++モジュールを作成し、GUIに開発者が生産性の高い言語を使用するのが最善です。
ゲームではないクロスプラットフォームアプリケーションは、この説明の範囲を超えています。 この場合、C ++もJavaも、効率的なGUI開発には冗長すぎるため、理想的ではありません。
つぶやき
一部のプロジェクトでは、選択が明確でない場合があるため、さらに比較してみましょう。
特徴 | C ++ | Java |
---|---|---|
初心者向け | いいえ | はい |
ランタイムパフォーマンス | 一番 | 良い |
レイテンシー | 予測可能な | 予測不可能な |
参照カウントスマートポインター | はい | いいえ |
グローバルなマークアンドスイープガベージコレクション | いいえ | 必須 |
スタックメモリの割り当て | はい | いいえ |
ネイティブ実行可能ファイルへのコンパイル | はい | いいえ |
Javaバイトコードへのコンパイル | いいえ | はい |
低レベルのオペレーティングシステムAPIとの直接の相互作用 | はい | Cコードが必要 |
Cライブラリとの直接の相互作用 | はい | Cコードが必要 |
Javaライブラリとの直接対話 | JNIを介して | はい |
標準化されたビルドとパッケージの管理 | いいえ | Maven |
表で比較されている機能の他に、多重継承、ジェネリック/テンプレート、リフレクションなどのオブジェクト指向プログラミング(OOP)機能にも焦点を当てます。 どちらの言語もOOPをサポートしていることに注意してください。Javaはそれを義務付けていますが、C++はグローバル関数と静的データとともにOOPをサポートしています。
多重継承
OOPでは、継承とは、子クラスが親クラスから属性とメソッドを継承することです。 1つの標準的な例は、より一般的なShape
クラスから継承するRectangle
クラスです。
// Note that we are in a C++ file class Shape { // Position int x, y; public: // The child class must override this pure virtual function virtual void draw() = 0; }; class Rectangle: public Shape { // Width and height int w, h; public: void draw(); };
多重継承とは、子クラスが複数の親から継承する場合です。 Rectangle
クラスとShape
クラス、および追加のClickable
クラスを使用した例を次に示します。
// Not recommended class Shape {...}; class Rectangle: public Shape {...}; class Clickable { int xClick, yClick; public: virtual void click() = 0; }; class ClickableRectangle: public Rectangle, public Clickable { void click(); };
この場合、 Shape
( Rectangle
の基本タイプ)とClickable
の2つの基本タイプがあります。 ClickableRectangle
は両方から継承して、2つのオブジェクトタイプを構成します。
C++は多重継承をサポートしています。 Javaはそうではありません。 多重継承は、次のような特定のエッジケースで役立ちます。
- 高度なドメイン固有言語(DSL)の作成。
- コンパイル時に高度な計算を実行します。
- Javaでは単純に不可能な方法でプロジェクトタイプの安全性を向上させます。
ただし、多重継承を使用することは一般的に推奨されていません。 テンプレートメタプログラミングと組み合わせない限り、コードが複雑になり、パフォーマンスに影響を与える可能性があります。これは、最も経験豊富なC++プログラマーのみが行うのが最適です。
ジェネリックとテンプレート
任意のデータ型で機能するクラスの汎用バージョンは、コードの再利用に実用的です。 どちらの言語もこのサポートを提供します(Javaはジェネリック、C ++はテンプレート)が、C ++テンプレートの柔軟性により、高度なプログラミングがより安全で堅牢になります。 C ++コンパイラは、テンプレートでさまざまな型を使用するたびに、新しいカスタマイズされたクラスまたは関数を作成します。 さらに、C ++テンプレートは、最上位関数のパラメーターのタイプに基づいてカスタム関数を呼び出すことができるため、特定のデータタイプに特殊なコードを含めることができます。 これは、テンプレートの特殊化と呼ばれます。 Javaには同等の機能はありません。
対照的に、ジェネリックを使用する場合、Javaコンパイラは、型消去と呼ばれるプロセスを通じて、型のない一般的なオブジェクトを作成します。 Javaはコンパイル中に型チェックを実行しますが、プログラマーはその型パラメーターに基づいてジェネリッククラスまたはメソッドの動作を変更することはできません。 これをよりよく理解するために、C++のtemplate<class T1, class T2>
を使用する、一般的なstd::string format(std::string fmt, T1 item1, T2 item2)
関数の簡単な例を見てみましょう。私が作成したライブラリ:
std::string firstParameter = "A string"; int secondParameter = 123; // Format printed output as an eight-character-wide string and a hexadecimal value format("%8s %x", firstParameter, secondParameter); // Format printed output as two eight-character-wide strings format("%8s %8s", firstParameter, secondParameter);
C++はformat
関数をstd::string format(std::string fmt, std::string item1, int item2)
として生成しますが、Javaはitem1とitem1
の特定のstring
とint
オブジェクトタイプなしでそれを作成しitem2
。 この場合、C ++テンプレートは、最後の着信パラメーターがint
であることを認識しているため、2番目のformat
の呼び出しで必要なstd::to_string
変換を実行できます。 テンプレートがないと、2番目のformat
呼び出しのように数値を文字列として出力しようとするC ++ printf
ステートメントの動作が未定義になり、アプリケーションがクラッシュしたり、ガベージが出力されたりする可能性があります。 Java関数は、最初のformat
呼び出しで数値を文字列としてのみ処理でき、16進整数として直接フォーマットすることはありません。 これは簡単な例ですが、クラスやformat
関数を変更せずに、任意のクラスオブジェクトを処理するための特殊なテンプレートを選択するC++の機能を示しています。 ジェネリックスの代わりにリフレクションを使用してJavaで正しく出力を生成できますが、この方法は拡張性が低く、エラーが発生しやすくなります。
反射
Javaでは、クラスまたはクラスタイプで使用可能なメンバーなど、構造の詳細を(実行時に)見つけることができます。 この機能は反射と呼ばれます。おそらく、オブジェクトに鏡をかざして中身を確認するようなものだからです。 (詳細については、Oracleのリフレクションドキュメントを参照してください。)
C ++には完全な反映はありませんが、最新のC ++は実行時型情報(RTTI)を提供します。 RTTIを使用すると、特定のオブジェクトタイプを実行時に検出できますが、オブジェクトのメンバーなどの情報にアクセスすることはできません。
メモリ管理
C ++とJavaのもう1つの重要な違いは、メモリ管理です。これには2つの主要なアプローチがあります。手動。開発者はメモリを手動で追跡して解放する必要があります。 自動。ソフトウェアは、未使用のメモリをリサイクルするために、どのオブジェクトがまだ使用されているかを追跡します。 Javaでは、例としてガベージコレクションがあります。
Javaにはガベージコレクションされたメモリが必要であり、手動によるアプローチよりも簡単なメモリ管理を提供し、セキュリティの脆弱性の原因となる一般的なメモリ解放エラーを排除します。 C ++は、ネイティブで自動メモリ管理を提供しませんが、スマートポインタと呼ばれるガベージコレクションの形式をサポートします。 スマートポインタは参照カウントを使用し、正しく使用すれば安全でパフォーマンスが高くなります。 C ++は、オブジェクトの破壊時にリソースをクリーンアップまたは解放するデストラクタも提供します。
Javaはヒープ割り当てのみを提供しますが、C ++はヒープ割り当て( new
およびdelete
または古いC malloc
関数を使用)とスタック割り当ての両方をサポートします。 スタックは線形データ構造であり、ヒープはツリーベースであるため、スタック割り当てはヒープ割り当てよりも高速で安全です。したがって、スタックメモリの割り当てと解放ははるかに簡単です。
スタック割り当てに関連するC++のもう1つの利点は、Resource Acquisition Is Initialization(RAII)として知られるプログラミング手法です。 RAIIでは、参照などのリソースは、それらの制御オブジェクトのライフサイクルに関連付けられています。 リソースは、そのオブジェクトのライフサイクルの終わりに破棄されます。 RAIIは、手動で逆参照せずにC ++スマートポインターがどのように機能するかを示します。関数の先頭で参照されるスマートポインターは、関数を終了すると自動的に逆参照されます。 これがスマートポインタへの最後の参照である場合、接続されたメモリも解放されます。 Javaも同様のパターンを提供しますが、特に同じコードブロックに複数のリソースを作成する必要がある場合は、C++のRAIIよりも扱いにくいです。
ランタイムパフォーマンス
Javaの実行時パフォーマンスは安定していますが、実際のアプリケーションでは手動のメモリ管理がガベージコレクションよりも高速であるため、C++が依然として最高の地位を保っています。 Javaは、JITコンパイルにより、特定のコーナーケースでC ++を上回ることができますが、C++はほとんどの重要なケースに勝ちます。
特に、Javaの標準メモリライブラリは、C ++のヒープ割り当ての使用量の削減と比較して、その割り当てでガベージコレクタを過負荷にします。 ただし、Javaは依然として比較的高速であり、遅延が最大の懸念事項でない限り、たとえば、リアルタイムの制約があるゲームやアプリケーションでは許容できるはずです。
ビルドとパッケージの管理
Javaのパフォーマンスに欠けているものは、使いやすさで補います。 開発者の効率に影響を与える1つのコンポーネントは、ビルドとパッケージの管理です。つまり、プロジェクトをビルドし、外部の依存関係をアプリケーションに取り込む方法です。 Javaでは、Mavenと呼ばれるツールがこのプロセスをいくつかの簡単なステップに単純化し、IntelliJIDEAなどの多くのIDEと統合します。
ただし、C ++では、標準化されたパッケージリポジトリは存在しません。 アプリケーションでC++コードを構築するための標準化された方法すらありません。一部の開発者はVisualStudioを好みますが、他の開発者はCMakeまたは別のカスタムツールセットを使用します。 さらに複雑さを増すために、特定の商用C ++ライブラリはバイナリ形式であり、それらのライブラリをビルドプロセスに統合する一貫した方法はありません。 さらに、ビルド設定またはコンパイラバージョンのバリエーションにより、バイナリライブラリを機能させる際に問題が発生する可能性があります。
初心者にやさしい
C ++がJavaよりも初心者にやさしくない理由は、ビルドとパッケージ管理の摩擦だけではありません。 プログラマーは、C、アセンブリ言語、またはコンピューターの低レベルの動作に精通していない限り、C++を安全にデバッグおよび使用するのが難しい場合があります。 C ++を動力工具のように考えてください。多くのことを達成できますが、誤用すると危険です。
Javaの前述のメモリ管理アプローチにより、C++よりもはるかにアクセスしやすくなります。 Javaプログラマーは、言語がオブジェクトメモリを自動的に処理するため、オブジェクトメモリの解放について心配する必要はありません。
決定時間:C ++またはJava?
C ++とJavaの違いを詳しく調べたので、元の質問に戻ります。C++とJavaのどちらですか。 2つの言語を深く理解していても、万能の答えはありません。
低レベルのプログラミング概念に精通していないソフトウェアエンジニアは、ゲームなどのリアルタイムコンテキストを除いて、決定をC ++またはJavaのいずれかに制限する場合は、Javaを選択する方がよい場合があります。 一方、視野を広げようとしている開発者は、C++を選択することでより多くを学ぶことができます。
ただし、C ++とJavaの技術的な違いは、決定の小さな要因にすぎない可能性があります。 特定の種類の製品には、特定の選択が必要です。 それでもわからない場合は、フローチャートを参照できますが、最終的には第3言語を指す可能性があることに注意してください。