unique_ptr deleter overhead
Asked Answered
P

3

7

In normal C++ design, most objects can be deleted either by a delete statement, the free function, or a library-specific equivalent to free. For such objects, the unique_ptr Deleter implementation can be a stateless object that is eliminated through Empty Base Class Optimization. However, some libraries require using another object (which might contain a function pointer or some other context) to delete objects from that library.

typedef struct lib_object lib_object;

struct lib_api {
  lib_object (*createInstance)();
  void (*freeInstance)(lib_object *o);
};

One could wrap this in unique_ptr by storing a lib_api pointer as a data member in a custom Deleter, but if multiple lib_object instances needed to be managed, e.g. in a container, it would double the memory overhead of tracking the objects. What kind of pattern can be used to maintain RAII principles when dealing with this library, while still remaining memory efficient?

Pharisee answered 10/10, 2016 at 20:30 Comment(2)
Can you make the lib_api* a static member of the deleter class?Gramarye
I think you need to store freeInstance in a custom derived class of the container class and that your container would contain lib_object*. In the derived class you have to implement the destructor that would call freeInstance on every element.Autotomize
Q
5

If there is only ever one lib_api object, then you can have your deleter get a static pointer to it.

If there can be more than one lib_api object then you have no choice but to store a pointer to it in the Deleter.

Queridas answered 10/10, 2016 at 20:45 Comment(1)
I thought about this, and it seems global variables are the only useful solution.Pharisee
A
2

I use a custom deleter template for such objects.

template<typename T, T Function>
struct function_deleter
{
    template<typename U>
    auto operator()(U&& u) const noexcept(noexcept(Function(std::forward<U>(u))))
    {
        return Function(std::forward<U>(u));
    }
};

then you can have use your deleter call free:

unique_ptr<int, function_deleter<void(*)(void*), &free>> uniq;

And its size is still equal to one pointer. live demo

Come C++17 you'll be able to use auto for non-type template parameters, simplifying the code to:

template<auto Function>
struct function_deleter
{
    template<typename U>
    auto operator()(U&& u) const noexcept(noexcept(Function(std::forward<U>(u))))
    {
        return Function(std::forward<U>(u));
    }
};

and

unique_ptr<int, function_deleter<&call_free>> uniq;

live demo

Having this, in your case I'd keep a unique_ptr<pair<lib_object, lib_api>> with static deleter supporting this structure.

using lib_pair = pair<lib_object, lib_api>;

void lib_free(lib_pair* p){
    p->second.freeInstance(p->first);
    delete p;
}


using unique_lib_ptr = unique_ptr<lib_pair, function_deleter<void(*)(lib_pair*), &lib_free>>

This seems more cache-heavy but might just be your thing.

Allegorist answered 10/10, 2016 at 20:48 Comment(0)
A
0

There should exist a more elegant solution but I would write something like

template <class ContainerType>
class TObjectContainer : public ContainerType {
  public:
   TObjectContainer() = default;
   TObjectContainer(const TObjectContainer&); // should call createCopy 
   TObjectContainer(TObjectContainer&&) = default;
   ~TObjectContainer()
     { for (lib_object* element : *this)
         (*freeInstance)(element);
     }

  private:
   void (*freeInstance)(lib_object *o);
};

typedef TObjectContainer<std::vector<lib_object*>> ObjectVector;

It does not use unique_ptr but it should basically do the job.

Note that your are likely to overload every removal method like clear to call freeInstance or pop_back to return your original std::unique_ptr.

Autotomize answered 10/10, 2016 at 20:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.