インターフェイスクラスは、C ++プログラムで非常に広く使用されています。 ただし、残念ながら、インターフェイスクラスに基づいたソリューションを実装する場合、多くの場合、ミスが発生します。 この記事では、インターフェイスクラスを正しく設計する方法について説明していますが、いくつかのオプションが検討されています。 スマートポインターの使用について詳しく説明します。 インターフェイスクラスに基づく例外クラスとコレクションクラステンプレートの実装の例を示します。
目次
1.特別なメンバー関数、オブジェクトの作成と削除
1.1。 特別なメンバー関数
1.2。 オブジェクトの作成と削除-基本的な詳細
1.3。 デストラクタアクセスレベル
1.4。 1つのモジュールで作成と削除
1.5。 ポリモーフィックな削除
1.6。 クラス宣言が不完全な場合の削除
2.純粋に仮想的な関数と抽象クラス
2.1。 純粋な仮想機能
2.2。 抽象クラス
2.3。 純粋な仮想デストラクタ
3.インターフェイスクラス
3.1。 実装
3.2。 オブジェクト作成
3.3。 オブジェクトを削除
3.3.1。 削除演算子を使用する
3.3.2。 特別な仮想機能を使用する
3.3.3。 外部関数を使用する
3.3.4。 スマートポインターを使用した自動削除
3.4。 実装クラスのインスタンスのライフタイムを管理するその他のオプション
3.5。 セマンティクスのコピー
3.6。 インターフェイスクラスコンストラクター
3.7。 双方向の相互作用
3.8。 スマートポインター
3.9。 定数メンバー関数
3.10。 COMインターフェイス
3.11。 インターフェイスクラスとライブラリ
4.インターフェイスクラスとその実装の例
4.1。 インターフェースクラス
4.2。 実装クラス
4.3。 標準のスマートポインター
4.3.1。 クライアント側の作成
4.3.2。 実装側の作成
4.4。 代替の基本クラスの実装
5.インターフェースクラスを使用して実装された例外とコレクション
5.1例外
5.2コレクション
6.インターフェイスクラスとラッパークラス
7.まとめ
参照資料
はじめに
インターフェイスクラスは、データを持たないクラスであり、主に純粋な仮想関数で構成されます。 このソリューションを使用すると、実装をインターフェイスから完全に分離できます。クライアントはインターフェイスクラスを使用します。別の場所では、純粋に仮想関数が再定義され、ファクトリ関数が定義される派生クラスが作成されます。 実装の詳細はクライアントから完全に隠されています。 このようにして、通常のクラスでは不可能な真のカプセル化が実装されます。 インターフェイスクラスについては、Scott Meyers [Meyers2]から読むことができます。 インターフェイスクラスは、プロトコルクラスとも呼ばれます。
インターフェイスクラスを使用すると、プロジェクトのさまざまな部分間の依存関係を弱めることができ、チーム開発が簡素化され、コンパイル/アセンブリ時間が短縮されます。 インターフェイスクラスを使用すると、実行時にモジュールを選択的にロードするときに、柔軟で動的なソリューションを簡単に実装できます。 インターフェイスクラスをインターフェイス(API)ライブラリ(SDK)として使用すると、バイナリ互換性の問題の解決が簡単になります。
インターフェイスクラスは非常に広く使用されており、ライブラリ(SDK)のインターフェイス(API)、プラグイン(プラグイン)などのインターフェイスを実装するのに役立ちます。 Gang of Four [GoF]パターンの多くは、インターフェイスクラスを使用して自然に実装されます。 インターフェイスクラスには、COMインターフェイスが含まれます。 ただし、残念ながら、インターフェイスクラスに基づいたソリューションを実装する場合、多くの場合、ミスが発生します。 この問題を明確にしてみましょう。
1.特別なメンバー関数、オブジェクトの作成と削除
このセクションでは、インターフェイスクラスに提供されるソリューションを完全に理解するために知っておく必要のあるC ++機能のいくつかについて簡単に説明します。
1.1。 特別なメンバー関数
プログラマーが次のリスト(デフォルトコンストラクター、コピーコンストラクター、コピー代入演算子、デストラクタ)からクラスのメンバー関数を定義していない場合は、コンパイラーがこれを行うことができます。 C ++ 11では、このリストに移動コンストラクターと移動割り当て演算子が追加されました。 これらのメンバー関数は、特別なメンバー関数と呼ばれます。 それらは使用される場合にのみ生成され、各機能に固有の追加の条件が満たされます。 この使用が完全に隠されている可能性があることに注意してください(たとえば、継承を実装する場合)。 必要な機能を生成できない場合、エラーが生成されます。 (再配置操作を除き、それらはコピー操作に置き換えられます。)コンパイラーによって生成されるメンバー関数は、パブリックで埋め込み可能です。
特別なメンバー関数は継承されません。派生クラスで特別なメンバー関数が必要な場合、コンパイラは常にそれを生成しようとします。プログラマーが基本クラスで定義した対応するメンバー関数の存在はこれに影響しません。
プログラマーは、特別なメンバー関数の生成を禁止できます。C++ 11では、C ++ 98で、対応するメンバー関数をprivateとして宣言し、定義しない場合、 "=delete"
構文を使用する必要があります。 クラス継承では、基本クラスで作成された特別なメンバー関数の生成の禁止は、すべての派生クラスに適用されます。
プログラマーがコンパイラーによって生成されたメンバー関数に慣れている場合、C ++ 11では、宣言をドロップするだけでなく、これを明示的に示すことができます。 これを行うには、宣言時に"=default"
構文を使用する必要がありますが、コードは読みやすく、アクセスレベルの制御に関連する追加機能が表示されます。
特別なメンバー関数の詳細は[Meyers3]にあります。
1.2。 オブジェクトの作成と削除-基本的な詳細
new/delete
演算子を使用してオブジェクトを作成および削除することは、一般的なツーインワン操作です。 new
呼び出すと、最初にオブジェクトにメモリが割り当てられます。 選択が成功すると、コンストラクターが呼び出されます。 コンストラクターが例外をスローすると、割り当てられたメモリは解放されます。 delete
演算子が呼び出されると、すべてが逆の順序で行われます。最初にデストラクタが呼び出され、次にメモリが解放されます。 デストラクタは例外をスローしないでください。
new
演算子を使用してオブジェクトの配列を作成する場合、メモリは最初に配列全体に割り当てられます。 選択が成功すると、ゼロから始まる配列の各要素に対してデフォルトのコンストラクターが呼び出されます。 コンストラクターが例外をスローした場合、配列の作成されたすべての要素について、コンストラクターの呼び出しの逆の順序でデストラクタが呼び出され、割り当てられたメモリが解放されます。 配列を削除するには、 delete[]
演算子(配列のdelete
演算子と呼ばれる)を呼び出す必要があります。配列のすべての要素について、コンストラクターの呼び出しと逆の順序でデストラクタが呼び出され、割り当てられたメモリが解放されます。
注意! 単一のオブジェクトまたは配列が削除されるかどうかに応じて、 delete
演算子の正しい形式を呼び出す必要があります。 このルールは厳守する必要があります。そうしないと、未定義の動作が発生する可能性があります。つまり、メモリリーク、クラッシュなど、何でも起こります。 詳細については、[Meyers2]を参照してください。
標準のメモリ割り当て関数は、要求を満たすことがstd::bad_alloc
ず、 std::bad_alloc
型の例外をスローします。
任意の形式のdelete
演算子をNULLポインターに適用しても安全です。
上記の説明では、1つの説明が必要です。 いわゆる単純型(組み込み型、Cスタイル構造体)の場合、コンストラクターは呼び出されず、デストラクタはどのような場合でも何もしません。 セクション1.6も参照してください。
1.3。 デストラクタアクセスレベル
delete
演算子がクラスへのポインターに適用されるとき、そのクラスのデストラクタはdelete
利用可能でなければなりません。 (セクション1.6で説明したこの規則にはいくつかの例外があります。)したがって、デストラクタを安全またはクローズにすることにより、プログラマはデストラクタが利用できない場合にdelete
演算子の使用を禁止します。 デストラクタがクラスで定義されていない場合、コンパイラは独自にこれを行い、このデストラクタは開かれます(セクション1.1を参照)。
1.4。 1つのモジュールで作成と削除
new
オペレーターがオブジェクトを作成した場合、それをdelete
オペレーターの呼び出しは同じモジュール内になければなりません。 比Fig的に言えば、「あなたがそれを取った場所に置いてください」。 この規則はよく知られています;例えば、[Sutter / Alexandrescu]を参照してください。 この規則に違反すると、メモリの割り当てと解放の機能の「不一致」が発生する可能性があり、原則として、プログラムが異常終了します。
1.5。 ポリモーフィックな削除
delete
演算子を使用してインスタンスが削除されるクラスのポリモーフィックな階層を設計している場合、基本クラスに開いている仮想デストラクタが必要です。これにより、 delete
演算子が基本クラスへのポインタに適用されるときに、オブジェクトの実際のタイプのデストラクタが呼び出されます。 この規則に違反すると、基本クラスのデストラクターの呼び出しが発生し、リソースリークが発生する可能性があります。
1.6。 クラス宣言が不完全な場合の削除
delete
演算子の雑多さは、特定の問題を引き起こす可能性があります; void*
型のポインターまたは不完全な(プリエンプティブ)宣言を持つクラスへのポインターに適用できます。 この場合、エラーは発生せず、デストラクタの呼び出しだけがスキップされ、メモリを解放する関数のみが呼び出されます。 例を考えてみましょう:
class X; // X* CreateX(); void Foo() { X* p = CreateX(); delete p; }
このコードは、完全なX
クラス宣言がdelete
ダイヤルピアで利用できない場合でもコンパイルされます。 ただし、コンパイル時に(Visual Studio)、警告が発行されます。
warning C4150: deletion of pointer to incomplete type 'X'; no destructor called
X
およびCreateX()
実装がある場合、コードはCreateX()
ますCreateX()
がnew
演算子によって作成されたオブジェクトへのポインターを返す場合、 Foo()
呼び出しFoo()
正常に実行され、デストラクタは呼び出されません。 これがリソースの流出につながる可能性があることは明らかであるため、警告に注意する必要があることを改めて説明します。
このような状況は決して大げさなものではなく、スマートポインターや記述子クラスなどのクラスを使用すると簡単に発生します。 Scott Meyersは[Meyers3]でこの問題を扱っています。
2.純粋に仮想的な関数と抽象クラス
インターフェイスクラスの概念は、純粋な仮想関数や抽象クラスなどのC ++の概念に基づいています。
2.1。 純粋な仮想機能
"=0"
構造を使用して宣言された仮想関数は、純粋仮想と呼ばれます。
class X { // ... virtual void Foo() = 0; };
通常の仮想関数とは異なり、純粋な仮想関数は定義できません(デストラクタを除き、セクション2.3を参照)が、派生クラスの1つで再定義する必要があります。
純粋に仮想関数を定義できます。 エンブレムサッターは、この機能のいくつかの便利な使用法を提供します[シャッター]。
2.2。 抽象クラス
抽象クラスは、少なくとも1つの純粋な仮想関数を持つクラスです。 抽象クラスから派生し、少なくとも1つの純粋仮想関数をオーバーライドしないクラスも抽象になります。 C ++標準では、抽象クラスのインスタンスの作成は禁止されており、非抽象クラスの派生物のインスタンスのみを作成できます。 したがって、抽象クラスが作成され、基本クラスとして使用されます。 したがって、コンストラクターが抽象クラスで定義されている場合、コンストラクターを開くことは意味がありません。保護する必要があります。
2.3。 純粋な仮想デストラクタ
場合によっては、純粋な仮想デストラクタを作成することをお勧めします。 ただし、このソリューションには2つの機能があります。
- 純粋に仮想的なデストラクタを定義する必要があります。 (通常、デフォルトの定義が使用されます。つまり、
"=default"
構造を使用し"=default"
。)派生クラスデストラクタは、継承チェーン全体に沿って基本クラスデストラクタを呼び出します。したがって、キューはルート(純粋に仮想デストラクタ)に到達することが保証されます。 - プログラマが派生クラスで純粋な仮想デストラクタを再定義していない場合、コンパイラはそれを彼のために行います(セクション1.1を参照)。 したがって、純粋に仮想的なデストラクタを持つ抽象クラスから派生したクラスは、デストラクタを明示的に再定義しなくても抽象性を失う可能性があります。
純粋な仮想デストラクタの使用例は、セクション4.4にあります。
3.インターフェイスクラス
インターフェイスクラスは、データを持たない抽象クラスであり、主に純粋な仮想関数で構成されています。 このようなクラスは、デストラクタなどの通常の仮想関数(純粋に仮想ではない)を持つことができます。 ファクトリー関数などの静的メンバー関数もあります。
3.1。 実装
インターフェイスクラスの実装は、純粋に仮想関数が再定義される派生クラスになります。 同じインターフェイスクラスの実装は複数存在する可能性があり、2つのスキームが可能です。複数の異なるクラスが同じインターフェイスクラスを継承する場合は水平、インターフェイスクラスがポリモーフィック階層のルートである場合は垂直です。 もちろん、ハイブリッドもあります。
インターフェイスクラスの概念のキーポイントは、インターフェイスを実装から完全に分離することです。クライアントはインターフェイスクラスでのみ動作し、実装は利用できません。
3.2。 オブジェクト作成
実装クラスにアクセスできないと、オブジェクトの作成時に特定の問題が発生します。 クライアントは、実装クラスのインスタンスを作成し、オブジェクトへのアクセスに使用するインターフェイスクラスへのポインターを取得する必要があります。 実装クラスが使用できないため、コンストラクターを使用できないため、実装側で定義されているファクトリー関数が使用されます。 この関数は通常、 new
演算子を使用してオブジェクトを作成し、作成されたオブジェクトへのポインターを返し、インターフェイスクラスへのポインターにキャストします。 ファクトリー関数は、インターフェイスクラスの静的メンバーにすることができますが、これは必ずしも必要ではありません。たとえば、特別なファクトリークラス(それ自体がインターフェイスクラスになることもあります)またはフリー関数のメンバーにすることができます。 ファクトリ関数は、インターフェイスクラスへの生のポインタではなく、インテリジェントなポインタを返すことができます。 このオプションについては、セクション3.3.4および4.3.2で説明します。
3.3。 オブジェクトを削除
オブジェクトの削除は非常に重要な操作です。 エラーが発生すると、メモリリークまたは二重削除が発生し、通常はプログラムがクラッシュします。 以下では、この問題を可能な限り詳細に検討し、誤った顧客行動の防止に多くの注意を払っています。
4つの主なオプションがあります。
-
delete
演算子を使用します。 - 特別な仮想関数を使用します。
- 外部関数を使用します。
- スマートポインターを使用した自動削除。
3.3.1。 delete
演算子を使用delete
これを行うには、インターフェイスクラスに開いている仮想デストラクタが必要です。 この場合、クライアント側のインターフェイスクラスへのポインタを呼び出すdelete
演算子は、実装クラスのデストラクタへの呼び出しを提供します。 このオプションは機能する場合がありますが、成功したと認識するのは困難です。 new
呼び出しを取得し、「障壁」の異なる側で演算子をdelete
します。実装側でnew
、クライアント側でdelete
します。 また、インターフェイスクラスの実装が別のモジュールで行われた場合(これは非常に一般的なことです)、セクション1.4のルール違反が発生します。
3.3.2。 特別な仮想関数を使用する
別のオプションとして、より進歩的な方法があります。インターフェイスクラスには、オブジェクトを削除する特別な仮想関数が必要です。 そのような関数は、最終的にdelete this
を呼び出すことになりますが、これは既に実装側で行われています。 このような関数は、たとえばDelete()
などのさまざまな方法で呼び出すことができますが、他のオプションも使用されます: Release()
、 Destroy()
、 Dispose()
、 Free()
、 Close()
など。 セクション1.4の規則に従うことに加えて、このオプションにはいくつかの追加の利点があります。
- 実装クラスにユーザー定義のメモリ割り当て/割り当て解除関数を使用できます。
- たとえば、参照カウンターを使用して、実装オブジェクトの有効期間を制御するためのより複雑なスキームを実装できます。
この実施形態では、 delete
演算子を使用してオブジェクトを削除しようとする試みはコンパイルされ、実行されることもあるが、これはエラーである。 インターフェイスクラスでそれを防ぐには、空または純粋に仮想的に保護されたデストラクタがあれば十分です(セクション1.3を参照)。 delete
演算子の使用は非常にマスクできることに注意してください。たとえば、標準のスマートポインターはdelete
演算子を使用してデフォルトでオブジェクトを削除し、対応するコードは実装に深く埋もれています。 保護されたデストラクタを使用すると、コンパイル段階でそのようなすべての試行を検出できます。
3.3.3。 外部関数を使用する
このオプションは、オブジェクトを作成および削除する手順の特定の対称性を引き付ける可能性がありますが、実際には以前のバージョンよりも利点はありませんが、多くの追加の問題があります。 このオプションの使用は推奨されておらず、これ以上考慮することはありません。
3.3.4。 スマートポインターを使用した自動削除
この場合、ファクトリ関数はインターフェイスクラスへの生のポインタではなく、対応するスマートポインタを返します。 このスマートポインターは実装側で作成され、削除オブジェクトをカプセル化します。削除オブジェクトは、スマートポインター(またはその最後のコピー)がクライアント側で範囲外になると、実装オブジェクトを自動的に削除します。 この場合、実装オブジェクトを削除するための特別な仮想関数は必要ないかもしれませんが、保護されたデストラクタが依然として必要であり、 delete
演算子の誤った使用を防ぐ必要があります。 (確かに、このようなエラーの可能性は著しく減少していることに注意すべきです。)このオプションについては、セクション4.3.2で詳しく説明します。
3.4。 実装クラスのインスタンスのライフタイムを管理するその他のオプション
場合によっては、クライアントはインターフェイスクラスへのポインタを受け取りますが、それを所有しません。 実装オブジェクトのライフタイムの管理は、実装側で完全に行われます。 たとえば、オブジェクトは静的なシングルトンオブジェクトにすることができます(このソリューションは工場で一般的です)。 別の例は双方向の相互作用に関連しています。セクション3.7を参照してください。 クライアントはそのようなオブジェクトを削除すべきではありませんが、そのようなインターフェースクラスの保護されたデストラクタが必要です。 delete
演算子の誤った使用を防ぐ必要があります。
3.5。 セマンティクスのコピー
インターフェイスクラスの場合、コピーコンストラクターを使用して実装オブジェクトのコピーを作成することはできないため、コピーが必要な場合、クラスには実装オブジェクトのコピーを作成し、インターフェイスクラスへのポインターを返す仮想関数が必要です。 このような関数はしばしば仮想コンストラクターと呼ばれ、その伝統的な名前はClone()
またはDuplicate()
です。
コピー割り当て演算子の使用は禁止されていませんが、良いアイデアとは見なされません。 コピー割り当て演算子は常にペアになります;コピーコンストラクターとペアにする必要があります。 デフォルトのコンパイラによって生成された演算子は無意味であり、何もしません。 理論的には、純粋に仮想の代入演算子を宣言し、その後にオーバーライドできますが、仮想代入は推奨されません;詳細は[Meyers1]にあります。 さらに、割り当ては非常に不自然に見えます。通常、実装クラスのオブジェクトへのアクセスは、インターフェイスクラスへのポインタを介して行われるため、割り当ては次のようになります。
* = *;
割り当て演算子は最も禁止されており、必要に応じて、そのようなセマンティクスはインターフェイスクラスに対応する仮想関数を持ちます。
割り当てを禁止するには2つの方法があります。
- 割り当て演算子を削除することを宣言します(
=delete
)。 インターフェイスクラスが階層を形成する場合、これは基本クラスで十分です。 このメソッドの欠点は、実装クラスに影響することです。禁止も適用されます。 - デフォルトの定義(
=default
)で保護された割り当てステートメントを宣言し=default
。 これは実装クラスには影響しませんが、インターフェイスクラスの階層の場合は、各クラスでそのようなアナウンスを行う必要があります。
3.6。 インターフェイスクラスコンストラクター
多くの場合、インターフェイスクラスコンストラクターは宣言されていません。 この場合、コンパイラーは、継承の実装に必要なデフォルトのコンストラクターを生成します(セクション1.1を参照)。 このコンストラクターは開いていますが、安全で十分です。 インターフェイスクラスで、コピーコンストラクターが削除済み( =delete
)として宣言されている場合、コンパイラーによるコンストラクターの生成はデフォルトで抑制されます。そのようなコンストラクターは明示的に宣言する必要があります。 デフォルトの定義( =default
)で保護するのが自然=default
。 原則として、このような保護されたコンストラクターの宣言は常に実行できます。 例はセクション4.4にあります。
3.7。 双方向の相互作用
インターフェイスクラスは、双方向通信を使用するのに便利です。 インターフェイスクラスを介してモジュールにアクセスできる場合、クライアントはインターフェイスクラスの実装を作成し、モジュールでそれらにポインターを渡すこともできます。 これらのポインターを介して、モジュールはクライアントからサービスを受信し、データまたは通知をクライアントに送信することもできます。
3.8。 スマートポインター
実装クラスのオブジェクトへのアクセスは通常、ポインターを介して行われるため、スマートポインターを使用してその寿命を制御するのは自然なことです。 ただし、オブジェクトを削除するための2番目のオプションを使用する場合は、標準のスマートポインターを使用して、ユーザー削除機能(タイプ)またはこのタイプのインスタンスを転送する必要があることに注意してください。 これが行われない場合、スマートポインターはdelete
演算子を使用してオブジェクトを削除し、コードは単純にコンパイルされません(保護されたデストラクタのおかげ)。 標準のスマートポインター(カスタムリムーバーの使用を含む)は、[Josuttis]、[Meyers3]で詳細に説明されています。 カスタムリムーバーの使用例は、セクション4.3.1にあります。
, , , .
3.9. -
- const. , , -, .
3.10. COM-
COM- , , COM — , COM- , C, , . COM- C++ , COM.
3.11.
(API) (SDK). . -, -, . , (Windows DLL), : -. . , , . LoadLibrary()
, -, .
4.
4.1。
, .
class IBase { protected: virtual ~IBase() = default; // public: virtual void Delete() = 0; // IBase& operator=(const IBase&) = delete; // };
.
class IActivatable : public IBase { protected: ~IActivatable() = default; // public: virtual void Activate(bool activate) = 0; static IActivatable* CreateInstance(); // - };
, , . , IBase
. , (. 1.3). , .
4.2.
class Activator : private IActivatable { // ... private: Activator(); protected: ~Activator(); public: void Delete() override; void Activate(bool activate) override; friend IActivatable* IActivatable::CreateInstance(); }; Activator::Activator() {/* ... */} Activator::~Activator() {/* ... */} void Activator::Delete() { delete this; } void Activator::Activate(bool activate) {/* ... */} IActivatable* IActivatable::CreateInstance() { return static_cast<IActivatable*>(new Activator()); }
, , , - , .
4.3.
4.3.1.
. - ( IBase
):
struct BaseDeleter { void operator()(IBase* p) const { p->Delete(); } };
std::unique_ptr<>
- :
template <class I> // I — IBase using UniquePtr = std::unique_ptr<I, BaseDeleter>;
, , - , UniquePtr
.
-:
template <class I> // I — - CreateInstance() UniquePtr<I> CreateInstance() { return UniquePtr<I>(I::CreateInstance()); }
:
template <class I> // I — IBase UniquePtr<I> ToPtr(I* p) { return UniquePtr<I>(p); }
std::shared_ptr<>
std::unique_ptr<>
, , std::shared_ptr<>
. Activator
.
auto un1 = CreateInstance<IActivatable>(); un1->Activate(true); auto un2 = ToPtr(IActivatable::CreateInstance()); un2->Activate(true); std::shared_ptr<IActivatable> sh = CreateInstance<IActivatable>(); sh->Activate(true);
( — -):
std::shared_ptr<IActivatable> sh2(IActivatable::CreateInstance());
std::make_shared<>()
, ( ).
: , . : , - . 4.4.
4.3.2.
. -. std::shared_ptr<>
, , ( ). std::shared_ptr<>
( ) - , delete
. std::shared_ptr<>
- ( ) - . .
#include <memory> class IActivatable; using ActPtr = std::shared_ptr<IActivatable>; // class IActivatable { protected: virtual ~IActivatable() = default; // IActivatable& operator=(const IActivatable&) = default; // public: virtual void Activate(bool activate) = 0; static ActPtr CreateInstance(); // - }; // class Activator : public IActivatable { // ... public: Activator(); // ~Activator(); // void Activate(bool activate) override; }; Activator::Activator() {/* ... */} Activator::~Activator() {/* ... */} void Activator::Activate(bool activate) {/* ... */} ActPtr IActivatable::CreateInstance() { return ActPtr(new Activator()); }
- std::make_shared<>()
:
ActPtr IActivatable::CreateInstance() { return std::make_shared<Activator>(); }
std::unique_ptr<>
, , - , .
4.4.
C# Java C++ «», . . IBase
.
class IBase { protected: IBase() = default; virtual ~IBase() = 0; // , virtual void Delete(); // public: IBase(const IBase&) = delete; // IBase& operator=(const IBase&) = delete; // struct Deleter // - { void operator()(IBase* p) const { p->Delete(); } }; friend struct IBase::Deleter; };
, Delete()
, .
IBase::~IBase() = default; void IBase::Delete() { delete this; }
IBase
. Delete()
, . - IBase
. Delete()
, - . Delete()
, . , 4.3.1.
5. ,
5.1
, , , , .
, , IException
Exception
.
class IException { friend class Exception; virtual IException* Clone() const = 0; virtual void Delete() = 0; protected: virtual ~IException() = default; public: virtual const char* What() const = 0; virtual int Code() const = 0; IException& operator=(const IException&) = delete; }; class Exception { IException* const m_Ptr; public: Exception(const char* what, int code); Exception(const Exception& src) : m_Ptr(src.m_Ptr->Clone()) {} ~Exception() { m_Ptr->Delete(); } const IException* Ptr() const { return m_Ptr; } };
Exception
, IException
. , throw
, . Exception
, . - , .
Exception
, , .
IException
:
class ExcImpl : IException { friend class Exception; const std::string m_What; const int m_Code; ExcImpl(const char* what, int code); ExcImpl(const ExcImpl&) = default; IException* Clone() const override; void Delete() override; protected: ~ExcImpl() = default; public: const char* What() const override; int Code() const override; }; ExcImpl::ExcImpl(const char* what, int code) : m_What(what), m_Code(code) {} IException* ExcImpl::Clone() const { return new ExcImpl(*this); } void ExcImpl::Delete() { delete this; } const char* ExcImpl::What() const { return m_What.c_str(); } int ExcImpl::Code() const { return m_Code; }
Exception
:
Exception::Exception(const char* what, int code) : m_Ptr(new ExcImpl(what, code)) {}
, — .NET — , — , C++/CLI. , , , C++/CLI.
5.2
- :
template <typename T> class ICollect { protected: virtual ~ICollect() = default; public: virtual ICollect<T>* Clone() const = 0; virtual void Delete() = 0; virtual bool IsEmpty() const = 0; virtual int GetCount() const = 0; virtual T& GetItem(int ind) = 0; virtual const T& GetItem(int ind) const = 0; ICollect<T>& operator=(const ICollect<T>&) = delete; };
, -, .
template <typename T> class ICollect; template <typename T> class Iterator; template <typename T> class Contain { typedef ICollect<T> CollType; CollType* m_Coll; public: typedef T value_type; Contain(CollType* coll); ~Contain(); // Contain(const Contain& src); Contain& operator=(const Contain& src); // Contain(Contain&& src); Contain& operator=(Contain&& src); bool mpty() const; int size() const; T& operator[](int ind); const T& operator[](int ind) const; Iterator<T> begin(); Iterator<T> end(); };
. , . , , , , - begin()
end()
, . (. [Josuttis]), for
. . , , .
6. -
. -, . . , ++. , .NET, Java Pyton. . , , . .NET Framework C++/CLI C++. .
7.
-, .
.
-
delete
. - .
- .
.
, delete
. , .
- , . , , delete
.
.
, , , , .
[GoF]
., ., ., . - . .: . 英語から — .: , 2001.
[Josuttis]
, . C++: , 2- .: . 英語から -M。:LLC "I.D. », 2014.
[Dewhurst]
, . C++. .: . 英語から — .: , 2012.
[Meyers1]
, . C++. 35 .: . 英語から — .: , 2000.
[Meyers2]
, . C++. 55 .: . 英語から — .: , 2014.
[Meyers3]
, . C++: 42 C++11 C++14.: . 英語から -M。:LLC "I.D. », 2016.
[Sutter]
, . C++.: . 英語から — : «.. », 2015.
[Sutter/Alexandrescu]
, . , . ++.: . 英語から -M。:LLC "I.D. », 2015.