非常に使いにくいだけでなく、非常に便利なCライブラリの周りに拡張可能なC++ラッパーを作成しました。目標は、オブジェクトの割り当て、プロパティの公開、オブジェクトの割り当て解除、セマンティクスのコピーなどのためにc ++の便利さを持たせることです...
問題はこれです:時々、Cライブラリーは基礎となるオブジェクト(オブジェクトへのポインター)を必要とし、クラスデストラクタは基礎となるメモリを破壊してはなりません。ほとんどの場合、デストラクタは基になるオブジェクトの割り当てを解除する必要があります。クラスでbool hasOwnership
フラグを設定して、デストラクタ、代入演算子などが、基礎となるメモリを解放する必要があるかどうかを確認できるように実験しました。ただし、これはユーザーにとって煩雑であり、別のプロセスがそのメモリをいつ使用するかを知る方法がない場合もあります。
現在、割り当てが基になる型と同じ型のポインターから来たときに、hasOwnershipフラグを設定するように設定しています。オーバーロードされたコンストラクターがcライブラリーからのポインターを使用して呼び出された場合も、同じことを行います。それでも、ユーザーがオブジェクトを作成し、c_apiを呼び出す関数の1つにオブジェクトを渡した場合、これはまだ処理されず、ライブラリは後で使用するためにポインタを格納します。オブジェクトを削除した場合、Cライブラリでsegfaultが発生することは間違いありません。
このプロセスを簡略化するデザインパターンはありますか?多分ある種の参照カウント?
動的に割り当てられたものをクリーンアップする責任が、使用方法に基づいてラッパークラスとCライブラリの間でシフトする場合、不適切に設計されたCライブラリを扱っているか、ラッパークラスであまりにも多くのことを実行しようとしています。
最初のケースでは、誰がクリーンアップの責任者であるかを追跡し、間違いが起こらないことを望みます(あなたまたはCライブラリのメンテナのどちらかによって)。
2番目のケースでは、ラッパーの設計を再考する必要があります。すべての機能が同じクラスに属していますか、それとも複数のクラスに分割できますか?おそらく、Cライブラリは Facadeデザインパターン に似たものを使用しているため、C++ラッパーで同様の構造を維持する必要があります。
いずれにせよ、Cライブラリが一部のもののクリーンアップを担当している場合でも、そのものへの参照/ポインタを保持することに問題はありません。ポインタが何を参照しているかをクリーンアップする責任がないことを覚えておく必要があります。
多くの場合、次のようなパターンを使用できます。
class C {
public:
void foo() {
underlying_foo(handle.get());
}
void bar() {
// transfers ownership
underlying_bar(handle.release());
}
// use default copy/move constructor and assignment operator
private:
struct deleter {
void operator()(T* ptr) {
deleter_fn(ptr);
}
};
std::unique_ptr<T, deleter> handle;
};
release
を使用すると、明示的に所有権を譲渡できます。ただし、これは混乱を招くため、可能であれば回避する必要があります。
ほとんどのCライブラリには、C++に似たオブジェクトライフサイクル(オブジェクトの割り当て、アクセサ、破棄)があり、所有権を譲渡することなくC++パターンに適切にマッピングされます。
ユーザーが共有の所有権を必要とする場合は、shared_ptr
あなたのクラスで。自分で共有する所有権を実装しようとしないでください。
更新:所有権の譲渡をより明確にしたい場合は、参照修飾子を使用できます。
void bar() && { ... }
次に、ユーザーは次のような左辺値でbar
を呼び出す必要があります。
C o;
std::move(o).bar(); // transfer of ownership is explicit at call site
あなたの問題への直接的な答えは、スマートポインターです。スマートポインターを使用してCライブラリのメモリを保持し、ポインターがライブラリー内にもあるときに参照を追加する(そしてCライブラリが戻るときに参照を削除する)ことにより、参照カウントがゼロになると自動的にメモリを解放します(そしてその時だけ)
ライブラリが内部で物事を解放でき、これが発生する可能性のあるシナリオが十分に文書化されている場合、実行できることは、すでに行っているようにフラグを設定することだけです。