2

Having two functions for allocation and freeing a raw pointer, I want to use C++ to get dirty job done easily. I found two options to make unique_ptr handle it and neither of them looks good to me:

char *raw_alloc();
void raw_free(char *ptr);

int main (int argc, char *argv[])
{
    //First option:
    {
        std::unique_ptr<char, decltype(&raw_free)> ptr (raw_alloc(), raw_free);
        printf ("size1: %lu\n", sizeof(ptr) / sizeof(char *));
    }

    //Second option:
    {
        struct deleter { void operator()(char *ptr) { raw_free(ptr); } };
        std::unique_ptr<char, deleter> ptr (raw_alloc());
        printf ("size2: %lu\n", sizeof(ptr) / sizeof(char *));
    }
}

Output says that the first pointer is twice as large as the second; naturally it takes space to keep a pointer to the freeing function.

In the same time second option requires me to create stub deleter for my type. Of course, I can write a function template that does it for me:

template <typename T, void (*D)(T*)>
auto my_unique (T *ptr) {
    struct deleter { void operator()(T *ptr) { D(ptr); } };
    return unique_ptr<T,deleter>(ptr);
};

But why can't unique_ptr do it for me and just accept deleter function as second template argument? How do skilled C++ people handle raw pointers?

edmz
  • 8,220
  • 2
  • 26
  • 45
thesame
  • 103
  • 2
  • 9
  • 1
    What is "legacy" about plain pointers? If whatever you're interfacing with was written with C (not C++) in mind there's nothing "legacy" about them. – Matti Virkkunen Mar 05 '16 at 13:44
  • 1
    You are right. Changed "legacy" to "raw". – thesame Mar 05 '16 at 13:47
  • You're asking two different questions: why `std::unique_ptr` differentiates between types and actual deleters, and how to handle raw pointers. It would be more appropriate to narrow it down to one, also considering that the latter is a little opinion-based/too broad. – edmz Mar 05 '16 at 14:08

3 Answers3

1

I usually use the second approach, but I'd keep the Deleter class in the global scope near the declarations of raw_alloc and raw_free. It would probably help to define a raw_unique_ptr shorthand:

char *raw_alloc();
void raw_free(char *ptr);

struct raw_releter
{ void operator()(char * const ptr) const noexcept { raw_free(ptr); } };

using raw_unique_ptr = std::unique_ptr<char, raw_releter>;
jotik
  • 17,044
  • 13
  • 58
  • 123
0

You can wrap a raw pointer into class with overloaded class-specific new and delete operators:

char* raw_alloc()
{
    // Don't allocate anything, just a demonstration of approach
    return (char*)"Hello, World!";
}

void raw_free(char* ptr)
{
    cout << ptr << endl;
}

class WrappedRawPointer
{
public:
    static void* operator new(std::size_t)
    {
        return raw_alloc();
    }

    static void operator delete(void* ptr, std::size_t)
    {
        raw_free((char*)ptr);
    }
};

int main() {
    // Will print "Hello, World!" twice: once for explicit
    // printing and once for deleter.
    auto ptr = unique_ptr<WrappedRawPointer>(new WrappedRawPointer());
    cout << (const char*) ptr.get() << endl;
    return 0;
}

I believe that this approach is better since it decouples away allocation/deallocation logic, but at the same time doesn't impose any significant performance loses.

Roman Dobrovenskii
  • 935
  • 10
  • 23
  • I was hoping to avoid any helper classes, and keep my code tiny and clean. I need nothing more than calling deleter on leaving scope. – thesame Mar 05 '16 at 14:21
  • I don't feel that it is a good idea to avoid creating additional classes. If you use some pointers that are allocated and deallocated some special way, then you obviously have some special semantics for these pointers. So it feels better to make that special semantics explicit by encapsulating it. Custom deleter is always a class, and raw pointers is never good. – Roman Dobrovenskii Mar 05 '16 at 14:26
  • By the way, I updated the example so you can see how to use it with actually pointer semantics. – Roman Dobrovenskii Mar 05 '16 at 14:29
0

std::unique_ptr by default contains a different implementation for when it doesn't contain a deleter than for when it does. As the version with deletor stores the deleter as the passed template type, the actual question boils down to the deducing of the class template parameter from the constructor. (See this thread)

As you have mentioned, the first option takes more space than the second, however I would not consider this a problem as compilers can optimize quite a lot.

Using your implementation of my_unique() is a correct approach to reduce the size of std::unique_ptr as it doesn't store the ptr to the function, however it assumes a real function pointer, while it doesn't cover using lambdas as deleter. (This is not a problem, though it's a limitation of this approach)

Another approach is using an std::shared_ptr, which doesn't require to know the how the destructor looks like, however it always stores the deleter and will take a lot of memory at the advantage of not having to know the actual implementation of the deleter.

In my opinion, I would use something similar to your option 1 or 2 when the std::unique_ptr would be locally. If this would be part of the API, it's harder to choose an implementation, and all three options (option 1, 2 or std::shared_ptr) are interesting. I would go for the std::shared_ptr as you can combine it with other APIs which have their own deleter. (Consider a factory which transforms data in either JSON or XML, which both a different function to cleanup the char* to the generated stream)

A different approach, similar to the std::shared_ptr is creating a std::unique_ptr<T, std::function<void(T*)>>, which contains a performance overhead as result of the std::function and most likely a large memory overhead compared to the original options.

Community
  • 1
  • 1
JVApen
  • 11,008
  • 5
  • 31
  • 67