【问题标题】:Managing C type lifecycle using boost's shared_ptr?使用 boost 的 shared_ptr 管理 C 类型生命周期?
【发布时间】:2012-02-15 20:12:58
【问题描述】:

我有一个类似于How to manage object life time using Boost library smart pointers? 的问题,但就我而言,“对象”根本不是 C++ 对象,而是从 C API 返回/传递的不透明类型。该类型没有指针语义,即没有解引用;但是,它作为参数传递给 C API 中的其他函数。该类型还有一个明确的close API,必须调用它才能清理内部资源。

所以,我有一个 C API,类似于

opaque_legacy_type_t x;
XXopen(..., &x); // allocates/opens resource and fills out 'x' to be used later
XXdoSomethingWithResource(x, ...); // do something with resources related to 'x'
...more actions...
XXclose(x); // closes and cleans up resources related to 'x'

出于各种原因,在我的 C++ 代码中,我想管理 opaque_legacy_type_t 的“实例”,就像管理堆分配的对象实例一样,即具有与 boost::shared_ptr<> 类似的共享语义。似乎shared_ptr 提供了足够的服务,我可以通过这样做来管理调用XXclose

opaque_legacy_type_t x;
XXopen(..., &x);
boost::shared_ptr<opaque_legacy_type_t> managed(x, XXclose);

但是,由于opaque_legacy_type_t 没有指针语义,managed 的用法有点笨拙。

我想做的是有一个类似于shared_ptrmanaged_type,并且正在寻找不需要我全部写出来的想法。

编辑: 我在示例中纠正了我原来的错误。旧版 API 通过值而不是指针获取不透明类型。

【问题讨论】:

  • this question 会覆盖您的用例吗?
  • @Matt:这个问题几乎涵盖了用例,特别是我在一个小测试用例中实现的 RAIIFunc 模板。它没有涵盖的是共享方面,我可以获取一个 RAIIFunc 实例并将其分配给另一个实例,当最后一个 RAIIFunc 消失时,仍然只能调用一次 XXclose() 。我想我可以用 shared_ptr 包装 RAIIFunc 并以这种方式获取计数的主体,但如果我正在编写代码,我想避免堆分配。

标签: c++ boost shared-ptr object-lifetime


【解决方案1】:

由于所有旧版 API 都采用指向不透明类型的指针,因此您可以直接使用共享指针。关键是你不要在堆栈上声明原始结构,而是通过new分配它:

int main () {
    std::shared_ptr<opaque_legacy_type_t> x(new opaque_legacy_type_t,
        [](opaqeue_legacy_type_t* p) { XXClose(p); delete p; });
    XXopen(..., x.get());
    XXdoSomethingWithResource(x.get(), ...);
}


编辑:如果某些 API 通过值而不是指针来获取不透明类型,则传递取消引用的指针。
int main () {
    std::shared_ptr<opaque_legacy_type_t> x(new opaque_legacy_type_t,
        [](opaqeue_legacy_type_t* p) { XXClose(*p); delete p; });
    XXopen(..., x.get());
    XXdoSomethingWithResource(*x, ...);
}

【讨论】:

  • 实际上,只有 XXopen() 需要一个指向不透明类型的指针,因为对于 XXopen,它是一个“out”参数。 API 的其余部分按值获取类型。
  • 在您在问题中给出的示例中,您通过指向XXCloseXXdoSomethingWithResource 的指针传递不透明类型。无论如何,请参阅我即将进行的编辑。
  • 啊,粗鲁。当您键入示例而不是剪切和过去的真实代码时,就会发生这种情况:(
【解决方案2】:

您可以将 boost 智能指针与 pimpl idom 一起使用:

class shared_opaque_legacy_type_t {
    struct impl {
        opaque_legacy_type_t t;
        impl(...) { XXOpen(..., t); }
        ~impl(...) { XXClose(t); }
    }
    boost::shared_ptr<impl> _impl;
public:
    shared_opaque_lagacy_type_t(...) : _impl(new impl(...)) {}

    opaque_legacy_type_t* get() {
        return _impl->t;
    }
};


shared_opaque_legacy_type_t x(...);
XXdoSomethingWithResource(x.get(), ...);

缺点是您仍然可以调用XXclose(x.get()) 并使您的对象无效。

更新:已修复。 :-)

【讨论】:

  • 这可能是最接近我想要的,但我想尽可能避免堆分配。一些遗留的 API 调用可能发生在关键路径上,我宁愿在那里没有堆分配。
  • 我正在尝试类似的事情,但还没有到可以测试我的堆分配问题是否存在真正问题的地步。会回来报告的。
  • 您可以通过放置 new 来绕过堆分配。然而,这会使事情复杂化很多,并可能带来新的问题。
  • 有了新的展示位置,我最终也不得不管理那个。在这种情况下,更好的选择可能是使用从固定大小的 struct impl 池中提取的 struct impl 分配器。但直到我看到堆分配是否真的是一个务实的问题。可能会发现 shared_ptr 比堆分配更具负担。
  • [最后在这里跟进...] 在筛选了所有优秀的建议之后,这个似乎在我的情况下效果最好。事实证明,堆分配并没有那么繁重,以至于无法使用。我不喜欢的一件事是必须一遍又一遍地编写相同的代码,除了构造函数,所以我最终将 shared_opaque_legacy_type 变成了一个在遗留类型(duh)上参数化的模板,以及两个函数:creator/destroyer。我在调用站点使用 boost::bind() 处理了不同的签名,因此泛型中的签名只采用了遗留类型。
【解决方案3】:

您可以编写一个与 boost 一起使用的包装器,它将调用 ctor 中的 open() 和 dtor 中的 close()

【讨论】:

  • 在包装器中为您需要调用的 C API 中的每个相关函数创建一个成员函数,您甚至不需要公开 opaque 类型 - 您可以在包装器中保持私有.
  • 尽量避免编写这样的定制代码。为什么? API 中有几种不透明的遗留类型,具有广泛的 API。我不想自己用 C++ 重新转换 API(这不是我的 API)...我只想让使用它的代码更安全。
【解决方案4】:

我投票支持 Rob 的答案,该答案仅使用 shared_ptr 而没有包装器,但如果您真的想避免动态分配,这里有一个简单的小例子来说明如何做到这一点。

是一个模板,直接持有句柄,不做分配。您向构造函数传递一个创建不透明类型对象的函子,以及一个在需要销毁该类型时调用的删除器。它是可移动且不可复制的,因此现在需要共享引用计数。它实现了隐式转换运算符,因此您可以在使用所持有类型的值的地方使用它。

template<typename T,typename D>
class opaque_type_handle {
    T handle;
    D deleter;
    bool needs_delete;
public:
    template<typename F>
    opaque_type_handle(F f,D d) : handle(f()), deleter(d), needs_delete(true) {}

    opaque_type_handle(opaque_type_handle const &) = delete;
    opaque_type_handle &operator=(opaque_type_handle const &) = delete;

    opaque_type_handle(opaque_type_handle &&rhs) : handle(rhs.handle),deleter(rhs.deleter),needs_delete(true) {
        rhs.needs_delete = false;
    }
    opaque_type_handle &operator=(opaque_type_handle &&rhs) {
        handle = rhs.handle;
        deleter = rhs.deleter;
        needs_delete = true;
        rhs.needs_delete = false;
        returh *this;
    }

    ~opaque_type_handle() {
        if(needs_delete) {
            deleter(handle);
        }
    }

    operator T&() { return handle; }
    operator T() const { return handle; }
};

像这样使用它:

// wrap up the code for creating an opaque_legacy_type_t handle
typedef opaque_type_handle<opaque_legacy_type_t,decltype(&XXclose)> legacy_handle;

legacy_handle make_legacy_handle(...) {
    return legacy_handle(
        [](){
            opaque_legacy_type_t tmp;
            XXopen(..., &tmp);
            return tmp;
        },
        &XXclose
    );
}

legacy_handle x = make_legacy_handle(...);
XXdoSomethingWithResource(x,...);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-23
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多