2

私はこれを数日間調査していて、それを行う「正しい方法」とは何かを理解しようとしていると言って始めます。RAII /プールデザイン/スマートポインターを何度も調べて、明確な結論に至らなかった後(絶対的な「正しい方法」がない場合を除いて)、もっと知識のある人が私を正しい方向に向ける時が来たのではないかと思いました。 。

オブジェクトプールを構築していて、必要に応じて、クライアントコードがRAIIを使用できることを確認しようとしています。

関係するエンティティは3つあります。

  • リソース。構築に費用がかかり、再利用するのに半安価です(リリース時に状態を確認するのは簡単ではありません)。一部のC構造を独自に割り当てられたメモリ/リソースでラップするため、コピーが困難です。
  • ラッパー/ハンドル。リソースのラッパー。ctorでリソースが与えられ、dtorでリリースされます。
  • コントローラー/プール。リソースのプールを維持し、Wrapperを介して、またはクライアントの裁量で直接、クライアントによるそれらの使用を管理します。

私が思いついたものの簡単な例を以下に示します。関数DoSomethingElse()では、私が何を求めているかを確認できます。ラッパーへの参照を取得し、スコープの最後でそのdtorが呼び出され、リソースがプールに解放されます。

私の質問はの定義と関係がありFactory::GetResource()ます。ここに示す簡略化されたバージョンは、毎回新しいバージョンを割り当てるだけです。私の実際の実装では、プールで使用可能なリソースをチェックし(使用可能なリソースがない場合は作成します)、使用中としてマークを付け、そのリソースへの参照を返します。

リソースの適切なコピーコンストラクターを定義する必要はなく、値ではなく参照によって返されます。リソースは呼び出し元よりも長持ちすることが保証されており、コントローラーはアプリの存続期間を通じて所有権を維持します。ライフサイクル管理のためにクライアントコードに渡されることはありません。もちろん、クライアントが直接参照を要求した場合、つまりラッパーがない場合、すべての賭けは無効になります。

このデザインは音ですか?shared_ptrを使用したほうがよいでしょうか?または他のメカニズム/デザイン?

御時間ありがとうございます。

#include <iostream>
#include <vector>

using namespace std;

static int seq = 0; // POOR MAN'S SEQUENCE FOR INSTANCE IDs

class Resource
{
public:
    Resource() : id(seq++) { cout << "Resource ctor: " << id << endl; }
    ~Resource() { cout << "Resource dtor: " << id << endl; }
private:
    int id;
};

class Wrapper
{
public:
    // ON ACTUAL IMPLEMENTATION, NOTIFY THE CONTROLLER OF THE RELEASE
    ~Wrapper()
        { cout << "Wrapper dtor: " << id << "Welease Bwian! Ee, I mean, the wesouwce" << endl; }

    explicit Wrapper(Resource& r) : id(seq++), res(r)
      { cout << "Wrapper ctor: " << id << endl; }

    int getID() const { return id; }
private:
    int id;
    Resource& res;
};

class Controller
{
public:
    ~Controller() { for (auto r : allres) delete r; }
    Resource& GetResource();
private:
    // SIMPLIFIED. I'M USING Boost PTR CONTAINER
    vector<Resource *> allres;
};

// SIMPLIFIED. IT WOULD ACTUALLY GET A RESOURCE FROM THE POOL
Resource& Controller::GetResource()
{
    Resource* newres = new Resource();
    allres.push_back(newres);

    return *(newres);
}

// SIMULATE GLOBAL CONTEXT
Controller& GetController()
{
    static Controller f;
    return f;
}

void DoSomething(Wrapper& wr)
{
    cout << "DoSth INI" << endl;
    cout << wr.getID() << endl;
    cout << "DoSth END" << endl;
}

void DoSomethingElse()
{
    cout << "DoSthElse INI" << endl;
    Wrapper w(GetController().GetResource());
    DoSomething(w);
    cout << "DoSthElse END" << endl;
}

int main(int argc, char *argv[])
{
    cout << "main INI" << endl;
    cout << "Calling DoSthElse" << endl;
    DoSomethingElse();
    cout << "Called DoSthElse" << endl;
    cout << "main END" << endl;
}
4

2 に答える 2

2

RAIIは本当に所有権についてです。オブジェクトの所有者は誰ですか。オブジェクトの所有権を放棄したら、何をする必要がありますか。

あなたが説明している状況は、リソースが実際にはコントローラーによって所有されているということです。リソースオブジェクトの存続期間は、コントローラーによって管理されます。

リソースのユーザーは、事実上、リソースを「ロック」して「使用中」としてマークするだけですが、リソースの所有権は取得しません。それらはその寿命に影響を与えません。(彼らはロックを所有していると言うことができます、そしてそれ彼らが管理する必要があるリソースです)

std::unique_ptr<Resource>したがって、カスタム削除機能で作成されたのようなものを公開することをお勧めします。controller.getResource()(これは、呼び出しから値で返すことができます

ユーザーはこれunique_ptrを好きなように行うことができます。コピーはできませんが、移動できます。スコープから外れると、カスタム削除機能が呼び出され、コントローラーで「未使用」としてマークされ、効果的にプールに戻されます。

そうすることで、オブジェクトを値で返すことができます。これは、クライアントにとって便利で簡単に操作でき、「ラップされていない」リソースオブジェクトを公開することをまったく回避します。クライアントは常にオブジェクトをでラップするunique_ptrため、多くの可能性が排除されます。エラー。

于 2012-07-25T20:02:16.293 に答える
1

現在のコードでは、呼び出し元がラッパー/リソースの使用を完了したことをコントローラーが通知する方法がないことに注意してください。これは、Controller :: GetResourceの実装に取り​​掛かったときに、コントローラーが以前に作成されたリソースを返すことができるかどうかを知る方法がないことを意味します。

通常、このようなデザインには、ラッパーデストラクタで呼び出されるController::ReleaseResourceが含まれます。これは、ラッパーが構築されるとリソースを取得し、破棄されるとリソースを解放することを意味します。これはまさにRAIIです。

于 2012-07-24T19:54:54.527 に答える