3

I am dealing with C types which have a new_ and free_ methods associated.

The type signature of new_ may vary, but the free_ is always a void free_something(something*);

Currently, I am declaring my unique_ptrs this way, which seems overly verbose:

std::unique_ptr<qdr_link_t, decltype(&free_qdr_link_t)> link{new_qdr_link_t(), free_qdr_link_t};

Can I do this with less ceremony, somehow? I saw a neat solution for a situation when my deallocator is the std::free() function, at https://stackoverflow.com/a/43626234/1047788. I tried to create a version where I could parameterize the deallocator, but I got nowhere with it.

The best I could think of was to create a macro from the above declaration.

user7610
  • 25,267
  • 15
  • 124
  • 150
  • I have multiple types like this, and for each type, I have a `type * new_type(...)` and `void free_type(void)`. So yes to that. There aren't multiple `new_` with same name but different signature, because they are all declared in C and that does not permit overloading. – user7610 Dec 14 '20 at 15:01
  • 2
    How does `void free_type(void)` know what to free? Is the thing to be freed stored in a global? – Eljay Dec 14 '20 at 15:13
  • Silly me, the signature for free_ functions are `void free_type(*type)`. Sorry for getting it wrong previously. – user7610 Dec 14 '20 at 15:17
  • https://stackoverflow.com/questions/51252087/using-automatic-deduction-with-unique-ptr-and-custom-deleter – user7610 Feb 02 '22 at 00:28

2 Answers2

3

I've been using following:

template <auto fptr>
struct Caller {
    template <class... Args>
    auto
    operator()(Args&&... args) noexcept
    {
        return fptr(std::forward<Args...>(args)...);
    }
};

Example usage:

using qdr_freer = Caller<free_qdr_link_t>;

using unique_qdr_ptr = std::unique_ptr<qdr_link_t, qdr_freer>;

[[nodiscard]] unique_qdr_ptr
make_qdr_unique()
{
    return unique_qdr_ptr{new_qdr_link_t()};
}

This implementation uses C++17 features though, so it requires a few changes to work in C++11.


Note that although Caller may seem like a good fit with std::free, they are strictly speaking not compatible because std::free is a standard library function not designated as "addressable".

eerorika
  • 232,697
  • 12
  • 197
  • 326
1

Let the language do the hard work!

#include <memory>

struct qdr_link_t;
qdr_link_t* new_qdr_link_t();
void free_qdr_link_t(qdr_link_t*);

template <typename T, typename Deleter>
auto make_unique_ptr(T* raw, Deleter deleter)
{
    return std::unique_ptr<T, Deleter>(raw, deleter);
}

//std::unique_ptr<qdr_link_t, decltype(&free_qdr_link_t)> link{new_qdr_link_t(), free_qdr_link_t};
auto link = make_unique_ptr(new_qdr_link_t(), free_qdr_link_t);

Add std::forward to taste (if you care).


For C++11, you'll need to add the trailing return type -> std::unique_ptr<T, Deleter> to make_unique_ptr, or just put that in the "normal" return type.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35
  • 1
    It is a poor software engineering solution in terms of the size of `std::unique_ptr`. Each such a smart-pointer stores a pointer to the very same function, which is unnecessary. – Maxim Egorushkin Dec 14 '20 at 16:07
  • 1
    @MaximEgorushkin This is how `unique_ptr` works. It would be possible to wrap `free_qdr_link_t` in some callable type that "hardcodes" it and thus takes no runtime storage. But is there really a need for such complexity here? The best software engineering solution is the simplest, unless otherwise required. – Asteroids With Wings Dec 14 '20 at 16:09
  • 1
    If one took the trouble of using C++, that means they want an efficient rather than easy solution. For easy solution they can use Python. – Maxim Egorushkin Dec 14 '20 at 16:14
  • @MaximEgorushkin Writing the most complex solution possible for the "efficiency" of not storing a single function pointer, when you don't need to, is not the purpose of writing C++. "Use Python instead then" is... a silly suggestion, to be honest. C++ is only "trouble" if you insist on making it so. – Asteroids With Wings Dec 14 '20 at 16:19
  • My primary criterion is simplicity of use. I have large number of various C structs like `qd_link_t` in a program and I want to use scoped pointers for them in unittests written in C++. Saving memory on the pointers is not worth it, if it complicates usage at the callsite or if I have to manually declare wrapper callable type. The tests have much worse perf problems than this. – user7610 Dec 14 '20 at 16:21
  • Lol, Python is that "much worse perf problem" I alluded to before. The app under test is written in a mix of C and embedded Python. Any unittest has to first initialize Python, to do anything "useful". – user7610 Dec 14 '20 at 16:24
  • IMO, you wasted more ink defending your sub-optimal solution, then ink required to write an efficient solution. Feel free to disagree. – Maxim Egorushkin Dec 14 '20 at 16:51
  • @MaximEgorushkin I wouldn't have had to if you hadn't wasted "ink" on challenging a perfectly reasonable, working solution that the OP is happy with Feel free to write a more complex competing answer. ‍♂️ Have a good one. – Asteroids With Wings Dec 14 '20 at 16:57
  • (I'd actually appreciate something more "optimal", provided that it will be as simple to use as `auto link = make_unique_ptr(new_qdr_link_t(), free_qdr_link_t);` is. Any extra complexity can be hidden behind a function call, or other such C++ mechanism.) – user7610 Dec 14 '20 at 17:41
  • 1
    @user7610 Not really. At the very least you'd have to create a new type somewhere to wrap `free_qdr_link_t`, and do that for every other free-ing function you have too. It doesn't scale well. If that were so, I'd have shown it. This way, in my view, has the right balance of simplicity, readability, maintainability, usability, performance and extensibility. – Asteroids With Wings Dec 14 '20 at 18:03