ACオブジェクトのC ++ラッパーの優れたデザインパターン


8

非常に使いにくいだけでなく、非常に便利なCライブラリの周りに拡張可能なC ++ラッパーを作成しました。目標は、オブジェクトの割り当て、プロパティの公開、オブジェクトの割り当て解除、セマンティクスのコピーなどのためにc ++の便利さを持たせることです...

問題はこれです。Cライブラリは、基になるオブジェクト(オブジェクトへのポインタ)を必要とする場合があり、クラスデストラクタは、基になるメモリを破棄しないでください。ほとんどの場合、デストラクタは基になるオブジェクトの割り当てを解除する必要があります。bool hasOwnershipクラスにフラグを設定して、デストラクタ、代入演算子などが、基礎となるメモリを解放する必要があるかどうかがわかるように実験しました。ただし、これはユーザーにとって煩雑であり、別のプロセスがそのメモリをいつ使用するかを知る方法がない場合もあります。

現在、基本的な型と同じ型のポインターから割り当てが行われる場合に、hasOwnershipフラグを設定するように設定しています。オーバーロードされたコンストラクターがcライブラリーからのポインターを使用して呼び出されたときも、同じことを行います。それでも、ユーザーがオブジェクトを作成し、それをc_apiを呼び出す関数の1つに渡した場合、これはまだ処理されません。ライブラリは、後で使用するためにポインターを格納します。オブジェクトを削除した場合、Cライブラリでsegfaultが発生することは間違いありません。

このプロセスを簡略化するデザインパターンはありますか?多分ある種の参照カウント?


これらのオブジェクトのスレッドセーフティについては何も考えていないようです...
ラチェットフリーク

@ratchetfreakラッパーが行うことはすべて、アクセサー/ミューテーターを呼び出し、適切なコピーセマンティクスを確保し、メモリを処理し、リストをコピーするなどです。実際にはデータに直接触れません。Cライブラリはすでにスレッドセーフです。また、私のユーザーはコールバックのシステムと同期してラッパーにのみアクセスできます。必要に応じて、すべてのデータの読み取り/書き込みをロックします。
ジョナサンヘンソン2013年

2
unique_ptrほとんどの場合、すでにこのタイプのリソースを処理できるため、自分でリソース管理を実装する必要はありません。メソッドをunique_ptr使用してrelease、格納されたオブジェクトの所有権を放棄します。
フィリップ2013年

回答:


4

動的に割り当てられたものをクリーンアップする責任が、使用方法に基づいてラッパークラスとCライブラリの間でシフトする場合は、不適切に設計されたCライブラリを扱っているか、ラッパークラスで多くのことを実行しようとしています。

最初のケースでは、誰がクリーンアップの責任者であるかを追跡し、間違いが起こらないことを望みます(あなたまたはCライブラリのメンテナのどちらかによって)。

2番目のケースでは、ラッパーの設計を再考する必要があります。すべての機能が同じクラスに属していますか、それとも複数のクラスに分割できますか?おそらく、CライブラリはFacadeデザインパターンに似たものを使用しているため、C ++ラッパーでも同様の構造を維持する必要があります。

いずれにせよ、Cライブラリが一部のもののクリーンアップを担当している場合でも、そのものへの参照/ポインタを保持することに問題はありません。ポインタが何を参照しているかをクリーンアップする責任はないことを覚えておく必要があります。


libに渡されるときに、基になるオブジェクトのクローンが作成されたことを確認したらどうなるでしょうか。その後、私は好きなように私の記憶を解放することができ、それは何にも影響を与えません。ただし、その場合、クラスへのオブジェクトの変更は返されません。
ジョナサンヘンソン2013年

そして、はい、いくつかの非常に悪い習慣がこのlibで使用されました。たとえば、構造体に文字列を設定する関数にユーザーにconst char *を渡してから、構造体に格納するコピーを作成する代わりに、ユーザーはその場で文字列を割り当て、char *を渡すことを期待します。コピーせずに保存するだけです。これが、ラッパーを作成した主な理由の1つです。適切なコピーセマンティクスに従っていることを確認するためです。
ジョナサンヘンソン2013年

@JonathanHenson:クライアントが何かを割り当て、その所有権/責任をライブラリーに渡せるようにすることは、過剰なコピーを回避し、Cライブラリーに対して完全に有効な一般的な最適化手法です(それが一貫して行われている限り)。しかし実際には、C ++から使用する場合、ユーザーはCライブラリのインターフェイス要件に準拠するために追加のコピーを作成する必要がある可能性があることを意味します。
Bart van Ingen Schenau 2013年

3

多くの場合、次のようなパターンを使用できます。

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

@Phillip、それはまさに私が必要としているもののように見えます。私はこれを試して、あなたと一緒に戻ります。
ジョナサンヘンソン2013年

一つの質問。この場合、所有権とは正確には何ですか。それは、オブジェクトにアクセス/変更する特権を意味しますか、それとも単にオブジェクトを構築および破壊する能力を意味しますか?
ジョナサンヘンソン2013年

基本的に、基盤となるメモリをc-libに渡したら、メモリを無料で呼び出すことはできません。ただし、オブジェクト内のデータにアクセスする必要があります。
ジョナサンヘンソン2013年

2
所有権はオブジェクトを破壊する権利です。
DeadMG 2013年

@JonathanHenson:これは、このイディオムでは現在カバーされていない興味深いケースです。一度を解放するとunique_ptr、それが保持していたポインタは保存されなくなります。所有権の移転をモデル化すると同時に、非公開リソースへのアクセスを許可することは、ユーザーを混乱させるようです。Cライブラリが現在所有しているオブジェクトを解放するタイミングをどのように制御しますか?
フィリップ

0

あなたの問題への直接的な答えは、スマートポインターです。スマートポインターを使用してCライブラリのメモリを保持し、ポインターがライブラリー内にもあるときに参照を追加する(そしてCライブラリが戻るときに参照を削除する)ことにより、参照カウントがゼロになったときにメモリを自動的に解放します(そしてその時だけ)


私がスマートポインターをひどく誤解しない限り、これは問題に対処しません。問題は、デストラクタがメモリをクリーンアップする必要がある場合と、Cライブラリがメモリをクリーンアップする場合です。これらの構造には、特別なinit、free関数があります。ライブラリは、スマートポインターの参照カウントが0に達する前に、それ自体でfreeを呼び出す場合があります。基本的に、所有権を決定してそれを手放す必要があります。いつメモリをクリーンアップするかを知るのにスマートポインタは必要ありません。問題は、私がメモリをクリーンアップする責任を負わない場合があり、デストラクタで解放する必要がないということです。
ジョナサンヘンソン、2013年

はい、同意します。問題を誤解しました。ただ1つの観察ですが、異なるライブラリは同じメモリ割り当てテーブルを共有していますか?私の集中的なC / C ++のことは10年前でした。当時のVisual Studioでは、両方のライブラリのコンパイラオプションを変更しない限り、あるライブラリにメモリを割り当てて別のライブラリで解放すると、メモリリークが発生します。これを行うことの結果は、高度にスレッド化されたアプリケーションでのメモリ管理の競合になります。これはおそらく時代遅れの情報です。
Michael Shaw

すべてが同じアドレス空間にロードされている場合はそうではありません。とにかく、ラッパーと同じアセンブリでc-libを再コンパイルしています。
ジョナサンヘンソン2013年

0

ライブラリが内部で物事を解放でき、これが発生する可能性のあるシナリオが十分に文書化されている場合、実行できることは、すでに行っているようにフラグを設定することだけです。


基本的に、基盤となるメモリをc-libに渡したら、メモリを無料で呼び出すことはできません。ただし、オブジェクト内のデータにアクセスする必要があります。
ジョナサンヘンソン2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.