2024C#からC++C#からC++へのコード変換ルール:オブジェクトの作成とメソッド呼び出しC#とC++で書かれたコードの動作は異なる場合があります。Cs2Cppがこのような違いをどのように扱い、コード変換の正確性を保証するかを詳しく見ていきましょう。ユニットテストの変換がどのように行われるかも学びます。参照型オブジェクトを作成するために、MakeObject関数(std::make_sharedに相当)を使用します。これはnew演算子を使用してオブジェクトを作成し、直ちにSharedPtrでラップします。この関数を使用することで、生ポインターによって導入された問題を避けることができましたが、アクセス権の問題が発生しました:すべてのクラスの外にあるため、プライベートコンストラクターへのアクセスができませんでした。この関数を非公開コンストラクターを持つクラスのフレンドとして宣言することで、すべてのコンテキストでそのようなクラスを作成することが可能になりました。その結果、この関数の外部バージョンは公開コンストラクターでの使用に限定され、非公開コンストラクター用に同じアクセスレベルとプロキシされたコンストラクターと同じ引数を持つ静的MakeObjectメソッドが追加されました。C#プログラマーは、オブジェクト作成式の一部としてプロパティ初期化子をよく使用します。}());呼び出し、デリゲート、および匿名メソッドメソッドの呼び出しはそのまま転送されます。オーバーロードされたメソッドを扱う際には、C++とC#のオーバーロード解決ルールが異なるため、引数の型を明示的にキャストする必要がある場合があります。}};注:Callメソッド内のFooとBarへのメソッド呼び出しは異なって記述されています。ルールによりboolを受け取るBarのオーバーロードが呼び出されるためです。Fooメソッドの場合はそのような曖昧さがなく、トランスレータはよりシンプルなコードを生成します。振る舞いが異なるもう一つの例は、テンプレート展開です。C#では、型パラメータの置換は実行時に行われ、ジェネリックメソッド内の呼び出しの解決には影響しません。C++では、テンプレート引数の置換はコンパイル時に行われるため、C#の振る舞いをエミュレートする必要があります。Bar<System::String>(u"abc");}ここで重要なのは、FooとBarを呼び出す際にテンプレート引数を明示的に指定することです。最初のケースでは、そうしないと、T=System::Stringのバージョンをインスタンス化する際に、C#の振る舞いとは異なる非テンプレートバージョンが呼び出されるためです。二番目のケースでは、引数が必要です。なければ、文字列リテラルの型に基づいて推論されます。一般的に、トランスレータは予期しない振る舞いを避けるために、ほとんど常にテンプレート引数を明示的に指定する必要があります。多くの場合、トランスレータはC#にはない明示的な呼び出しを生成する必要があります。これは主に、プロパティやインデクサへのアクセスメソッドに関するものです。参照型のコンストラクタの呼び出しは、上記のようにMakeObjectでラップされます。.NETには、StringBuilder.Append()やConsole.WriteLine()など、params構文を使用して引数の数と型によるオーバーロードをサポートするメソッドがあります。このような構造の直接転送は、ボックス化と一時配列の作成のためにパフォーマンスが低下します。そのため、可変数の引数を任意の型で受け取るオーバーロードを追加し、型キャストや配列への統合なしに引数をそのまま翻訳します。その結果、このような呼び出しのパフォーマンスが向上します。デリゲートはMulticastDelegateテンプレートの特殊化に翻訳され、通常はstd::functionインスタンスのコンテナを内部に含んでいます。その呼び出し、格納、および割り当ては簡単に行われます。匿名メソッドはラムダ関数に変換されます。ラムダ関数を作成する際には、キャプチャされた変数と引数の寿命を延ばす必要があり、これによりコードが複雑になるため、トランスレータはラムダ関数が周囲のコンテキストを超えて生き残る可能性がある場合にのみこれを行います。この振る舞い(変数の寿命を延ばす、参照によるキャプチャまたは値によるキャプチャ)は、より最適なコードを得るために手動で制御することもできます。例外処理に関してC#の挙動をエミュレートすることは、決して簡単なことではありません。これは、C#とC++で例外の挙動が異なるためです:C#では、例外はヒープ上に作成され、ガベージコレクターによって削除されます。C++では、例外はスタックと専用のメモリ領域の間で異なるタイミングでコピーされます。これは矛盾を生じさせます。ArgumentException)を介して扱われる場合、メモリリークや削除ポイントの特定に関する重大な問題を引き起こす可能性があります。SharedPtr<ArgumentException>(MakeObject<ArgumentException>()))を介して所有される場合、SharedPtr<ArgumentException>はSharedPtr<Exception>から継承していないため、基本型によって例外をキャッチすることはできません。例外オブジェクトがスタック上に置かれる場合、基本型によって正しくキャッチされますが、基本型の変数に保存されると、最終型に関する情報が切り捨てられます。この問題を解決するために、特別なタイプのスマートポインターExceptionWrapperを作成しました。その主な特徴は、もしクラスArgumentExceptionがExceptionから継承している場合、ExceptionWrapper<ArgumentException>もExceptionWrapper<Exception>から継承するということです。ExceptionWrapperのインスタンスは、例外クラスのインスタンスのライフタイムを管理するために使用され、ExceptionWrapper型の切り捨ては、関連するException型の切り捨てにはつながりません。例外の投げる処理は、最終的な例外型でパラメータ化されたExceptionWrapperを作成し、それを投げるExceptionの子孫によってオーバーライドされた仮想メソッドによって処理されます。仮想的な性質により、ExceptionWrapper型が以前に切り捨てられていたとしても、正しいタイプの例外を投げることができ、例外オブジェクトとExceptionWrapperとのリンクはメモリリークを防ぎます。テスト私たちのフレームワークの強みの一つは、ソースコードだけでなく、それに対する単体テストも変換できる能力です。C#プログラマーはNUnitとxUnitフレームワークを使用します。翻訳者は、対応するテスト例をGoogleTestに変換し、TestまたはFactフラグでマークされたメソッドの構文のチェックと呼び出しを、それぞれのテスト関数から置き換えます。引数のないテストとTestCaseやTestCaseDataのような入力データの両方がサポートされています。以下に翻訳されたテストクラスの例を示します。