0

I realize that similar questions have been asked elsewhere, but I couldn't find an answer that's a good fit for my function signatures.

Consider this typical pair of C functions:

int initFoo(Options options, Foo* foo);
void freeFoo(Foo* foo);

initFoo takes some options and a pointer to an uninitialized Foo struct. It initializes this struct and returns a result code that indicates whether the initialization was successful. freeFoo frees an initialized Foo struct.

Now assume I want to use these C functions in my C++ code. I want to use RAII, so I need to construct a unique_ptr<Foo> that will automatically call freeFoo on destruction. The best approach I could come up with is this:

template<typename T>
using lambda_unique_ptr = std::unique_ptr<T, std::function<void(T*)>>;

lambda_unique_ptr<Foo> createFoo(Options options) {
    Foo* foo = new Foo();
    const int resultCode = initFoo(options, foo);
    if (resultCode != 0) throw ...;

    return lambda_unique_ptr<Foo>(foo, [](Foo* foo) {
        freeFoo(foo);
        delete foo;
    });
}

I'm certain that there must be a more elegant solution. Ideally, something more functional that doesn't require so many individual steps. What I find particularly ugly is the necessity to explicitly allocate the Foo struct on the heap, then explicitly free and delete it in two steps. Any ideas?

Daniel Wolf
  • 12,855
  • 13
  • 54
  • 80
  • 2
    Does `allocFoo` not really _allocate_ Foo? It expects a pointer to an already-allocated Foo? Does `freeFoo` not free the memory? – Drew Dormann Jun 08 '18 at 20:46
  • @DrewDormann: I made up those names to make the question more generic. In light of your comment, I renamed `allocFoo` to `initFoo`. And yes, `initFoo` expects a pointer to an existing struct. – Daniel Wolf Jun 08 '18 at 20:53
  • 1
    @DanielWolf So `initFoo` and `freeFoo` don't actually handle memory? They're like constructors and destructors for Foo? – Justin Jun 08 '18 at 20:54
  • 2
    Typically I would expect those calls to be symmetrical. That is `initFoo` - `unintFoo` (no dynamic memory management) or `allocFoo` - `freeFoo` (both initialization / unitialization and dynamic memory management) – user7860670 Jun 08 '18 at 20:55
  • Does `freeFoo` not free the memory? If it doesn't, I'd imagine the C code having as many steps as your C++ wrapper. – Drew Dormann Jun 08 '18 at 21:04
  • I'm sorry if the names I chose cause confusion. For a real example, consider [`ov_fopen`](https://xiph.org/vorbis/doc/vorbisfile/ov_fopen.html) and [`ov_clear`](https://xiph.org/vorbis/doc/vorbisfile/ov_clear.html) from the Vorbis library. – Daniel Wolf Jun 08 '18 at 21:24

1 Answers1

3

Can't you just wrap Foo in a class?

struct FooWrap {
    Foo foo;

    explicit FooWrap(Options options) {
        if (initFoo(options, &this->foo))
            throw ...;
    }

    ~FooWrap() {
        freeFoo(&this->foo);
    }

    // XXX: either implement or disable assignment and copy construction
};

Now you can choose whether to just define FooWrap x(42); as an automatic variable or whether to allocate it dynamically with new.

melpomene
  • 84,125
  • 8
  • 85
  • 148
  • I was going for a `unique_ptr` because of its established interface. But maybe you're right and this case calls for its own class. – Daniel Wolf Jun 08 '18 at 21:26
  • This is better than using a `unique_ptr` in two ways that I notice - it's faster to access than a pointer reference to the heap, and it can be copied as long as `Foo` can be copied. – Drew Dormann Jun 09 '18 at 01:10