2

我会先说我已经研究了几天,试图了解什么是“正确的方法”。在对 RAII/池设计/智能指针进行了大量谷歌搜索之后,并没有得出明确的结论(除了可能没有绝对的“正确方法”),我想也许是时候让更有知识的人为我指出正确的方向了.

我正在构建一个对象池,并且如果需要,我正在尝试确保客户端代码可以使用 RAII。

涉及3个实体:

  • 资源。构建成本高,重用成本低(在发布时检查其状态并非易事)。难以复制,因为它包装了一些 C 结构,具有自己分配的内存/资源。
  • 包装/手柄。资源的包装器。在 ctor 上获得资源,在 dtor 上释放它。
  • 控制器/池。维护资源池并管理客户通过 Wrapper 或由客户自行决定直接使用的资源。

我在下面展示了一个我想出的简化示例。在函数DoSomethingElse()中,您可以看到我所追求的 - 我获得了对 Wrapper 的引用,并且在作用域的末尾,它的 dtor 被调用,并且资源被释放回池中。

我的问题与Factory::GetResource(). 这里介绍的简化版本只是每次都分配一个新的;我的实际实现检查池中是否存在可用资源(如果没有可用资源,则创建一个),将其标记为正在使用,并返回对它的引用。

我宁愿避免为资源定义正确的复制ctor,因此通过引用而不是按值返回。资源保证比调用者更长寿,并且控制器在应用程序的整个生命周期中保持所有权 - 它们不会交给客户端代码进行生命周期管理。当然,如果客户要求直接引用,即没有 Wrapper,所有的赌注都没有了。

这个设计好听吗?使用 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 真的是关于所有权。谁拥有该对象,一旦他们放弃对它的所有权,他们需要做什么?

您描述的情况是资源真正控制器所有。资源对象的生命周期由控制器管理。

资源的用户实际上只是“锁定”了资源,将其标记为“正在使用”,但他们并不拥有它的所有权。它们不会影响它的寿命。(你可以说他们拥有一把锁,就是他们需要管理的资源)

所以我建议公开类似 a 的东西std::unique_ptr<Resource>,它是用自定义删除器创建的。(并且可以通过controller.getResource()调用的值返回

用户可以随心所欲地使用unique_ptr它:它不可复制,但可以移动,一旦超出范围,它就会调用其自定义删除器,在 Controller 中将其标记为“未使用”,有效地将其返回到池中

这样你就可以按值返回一个对象,这对客户端来说很好而且简单,并且你完全避免暴露“未包装”的资源对象:客户端总是将它们包装在 aunique_ptr中,这消除了很多潜在的错误。

于 2012-07-25T20:02:16.293 回答
1

请注意,在您当前的代码中,控制器无法判断调用者何时使用包装器/资源完成。这意味着当您开始实施 Controller::GetResource 时,控制器将无法知道它是否可以返回先前创建的资源。

通常这样的设计包括在 Wrapper 析构函数中调用的 Controller::ReleaseResource。这意味着当 Wrapper 被构建时它会获得一个资源,然后当它被破坏时它会释放该资源。这正是 RAII。

于 2012-07-24T19:54:54.527 回答