2

I have a question similar to How to manage object life time using Boost library smart pointers? but, in my case, the "object" isn't a C++ object at all, but an opaque type returned/passed out from a C API. The type does not have pointer semantics, i.e., there is no dereferencing; it is, however, passed as an argument to other functions in the C API. The type also has a definitive close API which must be called in order to clean up internal resources.

So, I have a C API that's something along the lines of

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'

For various reasons, in my C++ code I would like to manage "instances" of opaque_legacy_type_t much like I would manage heap-allocated object instances, i.e. with similar sharing semantics as boost::shared_ptr<>. It seems that shared_ptr offers enough that I can manage calling XXclose by doing this:

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

But, since opaque_legacy_type_t doesn't have pointer semantics, the usage of managed is a bit clumsy.

What I'd like to do is have something like a managed_type that is similar to shared_ptr, and am looking for ideas that don't require me to write it all.

EDIT: I corrected my original screw-up in the example. The legacy API takes the opaque type by value rather than by pointer.

Community
  • 1
  • 1
Chris Cleeland
  • 4,760
  • 3
  • 26
  • 28
  • 2
    Would [this question](http://stackoverflow.com/questions/2121607/any-raii-template-in-boost-or-c0x) cover your use case? – Matt Feb 15 '12 at 20:19
  • @Matt: That question *almost* covers the use case, specifically the RAIIFunc<> template, which I implemented in a small test case. What it doesn't cover is the sharing aspect, where I could take one instance of RAIIFunc<> and assign it to another, and still only get one call to XXclose() when the last RAIIFunc<> goes away. I suppose I could wrap the RAIIFunc<> with a shared_ptr<> and get the counted body that way, but if I'm writing code anyway, I'd like to avoid the heap allocation. – Chris Cleeland Feb 15 '12 at 22:04

4 Answers4

2

Since all of the legacy API take a pointer to the opaque type, you could use shared pointers directly. The key is for you to not declare the original structure on the stack, but rather allocate it via 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(), ...);
}


EDIT: If some API take the opaque type by value instead of pointer, then pass the dereferenced pointer.
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, ...);
}
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Actually, only the XXopen() takes a pointer to the opaque type, because for XXopen it's an "out" parameter. The rest of the API takes the type by value. – Chris Cleeland Feb 15 '12 at 20:49
  • In the example you gave in your question, you pass the opaque type by pointer to both `XXClose` and `XXdoSomethingWithResource`. Regardless, see my upcoming edit. – Robᵩ Feb 16 '12 at 00:43
  • Ah, crud. That's what happens when you type up the example rather than cut and past real code :( – Chris Cleeland Feb 16 '12 at 14:49
1

You could write a wrapper to use with boost that will call the open() in the ctor and the close() in the dtor.

crush
  • 16,713
  • 9
  • 59
  • 100
  • Create a member function in the wrapper for each related function in the C API that you need to call, and you don't even need to expose the opaque type - you can keep in private in the wrapper. – Mark Ransom Feb 15 '12 at 20:37
  • Trying to avoid writing bespoke code like this. Why? There are several opaque legacy types in the API with a wide API. I don't want to re-cast the API in C++ myself (it's not my API)...I just want to make the code that uses it a bit safer. – Chris Cleeland Feb 15 '12 at 22:12
1

You could use boost smart pointers together with the 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(), ...);

The drawback is that you could still call XXclose(x.get()) and invalidate your object.

UPDATE: Fixed it. :-)

znkr
  • 1,746
  • 11
  • 14
  • This may be the closest to what I want, though I'd like to avoid the heap alloc if possible. Some of the legacy API calls may happen on the critical path and I'd rather not have heap allocations in there. – Chris Cleeland Feb 16 '12 at 16:05
  • I'm trying something similar to this, but am not at a point yet where I can test to see if my heap allocation concerns present real problems or not. Will report back. – Chris Cleeland Feb 21 '12 at 21:56
  • You could get around the heap allocation with placement new. However, that would complicate things a lot and probably introduce new problems as well. – znkr Feb 22 '12 at 19:23
  • With placement new I'd end up having to manage *that*, too. Probably the better choice in that case would be to use an allocator for struct impl that pulled from a fixed-size pool of struct impl. But not until I see if the heap allocation is actually a pragmatic issue. Could turn out that the shared_ptr<> is more of a burden than the heap allocation. – Chris Cleeland Feb 22 '12 at 19:57
  • [Finally following up here...] After sifting through all of the excellent suggestions, this one seemed to work the best in my situation. As it turned out, the heap allocation wasn't so onerous as to prevent use. One thing I did not like was having to write the same code over and over and over except for the constructor, so I ended up turning shared_opaque_legacy_type into a template parameterized over the legacy type (duh), and two functions: creator/destroyer. I dealt with difference signatures by using boost::bind() at the call site so the signature in the generic took just the legacy type. – Chris Cleeland Mar 19 '12 at 13:12
  • FYI, http://stackoverflow.com/questions/9082987/boostbind-boostfunction-with-partial-args provided the idea for dealing with different signatures with partially-bound functions. – Chris Cleeland Mar 19 '12 at 13:18
0

I voted for Rob's answer that just uses a shared_ptr with no wrapper, but if you really want to avoid dynamic allocation here's a simple little example of how to do that.

It's a template that directly holds the handle and does no allocation. You pass the constructor a functor that creates an object of the opaque type, and a deleter to call when the type needs to be destroyed. It's movable and non-copyable so now shared reference count is needed. It implements implicit conversion operators so you can use it where you'd use a value of the held type.

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; }
};

Use it like so:

// 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,...);
bames53
  • 86,085
  • 15
  • 179
  • 244