8

I have some C API that handles object creation and destruction, it provides: createObject(...) and destroy(...). I want to wrap it into some more modern construction/destruction mechanisms and use them with smart pointers. I am afraid that at some point I will forget to destroy the object, or some exception will occur.

I am aware of custom deleter function for shared_ptr, but I can't explicitly call new, because createOjbect function handles initialization.

Can I use STL smart pointers in this situation? Do I have to, from scratch, implement a class with initialization in constructor, destruction in destructor and reference counting in this situation?

einpoklum
  • 86,754
  • 39
  • 223
  • 453
Piotr Barejko
  • 456
  • 2
  • 12
  • 2
    Why do you need ref counter in your class? Just call createObject in constructor, destroy in destructor and provide some static factory function that will return smart pointer to your class. Smart pointer will handle refcounter part. When your class is finally destructed - it will automatically call destroy – Artemy Vysotsky Aug 26 '17 at 08:00
  • What does createObject return? What does it take? Depending on your answer there can be improvments. – Yakk - Adam Nevraumont Aug 26 '17 at 11:48
  • Why would you even do that, you have to use `.get()` everywhere where you want to call C API in your wrapper. – Konrad Apr 05 '20 at 13:05

3 Answers3

9

The std::shared_ptr is fully capable to create and delete an object with cutstom creator and deleter, but instead of new you have to use the creator function.

Let's consider, we have the following creator and deleter:

typedef struct {
    int m_int;
    double m_double;
} Foo;

Foo* createObject(int i_val, double d_val) {
    Foo* output = (Foo*)malloc(sizeof(Foo));

    output->m_int = i_val;
    output->m_double = d_val;

    puts("Foo created.");
    return output;
}

void destroy(Foo* obj) {
    free(obj);
    puts("Foo destroyed.");        
}

To manage an instance of Foo created by functions above, simply do the following:

std::shared_ptr<Foo> foo(createObject(32, 3.14), destroy);

Using the std::shared_ptr is an overhead if you don't wish to share the object's ownership. In this case the std::unique_ptr is much better but for this type you have to define a custom deleter functor with which it can delete the managed Foo instance:

struct FooDeleter {
    void operator()(Foo* p) const {
        destroy(p);
    }
};
using FooWrapper = std::unique_ptr<Foo, FooDeleter>;

/* ... */

FooWrapper foo(createObject(32, 3.14));
Akira
  • 4,100
  • 3
  • 20
  • 39
  • 1
    I'd add that you can often just pass C's free as the deleter function. – Malcolm McLean Aug 26 '17 at 11:57
  • Why would you even do that, you have to use .get() everywhere where you want to call C API in your wrapper. How hard is it to just call destroy in destructor? It's less obvious than explicit C API call. – Konrad Apr 05 '20 at 13:06
  • 1
    @Konrad you can't "just" call destroy, because you also need to think about what happens if the class is copied or moved, and define or delete other special members. Using a small pointer class stoves that for you. Having to call `get()` is a small price to pay, you seem more concerned about a few characters than code which is correct and safe. – Jonathan Wakely Apr 05 '20 at 20:08
  • @JonathanWakely it doesn't apply if I never copy/move it – Konrad Apr 05 '20 at 21:49
6

C++17.

template<auto X> using constant_t=std::integral_constant<std::decay_t<decltype(X)>, X>
template<auto X> constexpr constant_t<X> constant{};
template<class T, auto dtor> using smart_unique_ptr=std::unique_ptr< T, constant_t<dtor> >;

Now suppose you have a C API wrapping Bob with Bob* createBob(some_args...) and destroyBob(Bob*):

using unique_bob=smart_unique_ptr< Bob, destroyBob >;
unique_bob make_unique_bob(some_args args){
  return unique_bob( createBob(args) );
}

a unique_bob can be implicitly moved into a shared_ptr<Bob>.

A bit of an extra assumption can make this work in C++14:

template<class T, void(*dtor)(T*)> using smart_unique_ptr=std::unique_ptr< T, std::integral_constant<decltype(dtor),dtor> >;

which assumes the dtor signature is void(T*).

In C++11 you have to write a new stateless function pointer dispatcher for zero overhead unqiue ptrs.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • I like your solution with `std::integral_constant`. – Akira Aug 27 '17 at 06:12
  • Why would you even do that, you have to use `.get()` everywhere where you want to call C API in your wrapper. How hard is it to just call `destroy` in destructor? It's less obvious than explicit C API call. – Konrad Apr 05 '20 at 13:05
  • @Konrad Failing to call `get()` the compiler tells you; your code doesn't compile. Yay, incorrect code didn't compile. Falling to call `destroy` your code silently leaks. Boo, incorrect code compiles. Things that are checked at compile time *don't happen* at runtime. This isn't about making code require fewer characters, it is about making code where entire categories of errors don't happen. – Yakk - Adam Nevraumont Apr 05 '20 at 23:30
1

Posting full solution for my case:

Based on @Akira's suggestions I wrapped it using shared pointer, because I want this object shared in many palaces, and lambda:

// coming from some API:
struct SomeStruct;
bool initializedata(SomeStruct **data);
bool destorycdata(SomeStruct **data);

class SomeStructWrapper
{
public:
    SomeStructWrapper()
    {
        SomeStruct* data;
        if(initializedata(&data))
        {
            m_data = std::shared_ptr<SomeStruct>(data, [](SomeStruct* ptr){
                destorycdata(&ptr);
            });
        }
        else
        {
            throw std::runtime_error("Data was not initalized");
        }
    }

    const SomeStruct* operator->() const {return m_data.get();}
    SomeStruct* operator->() {return m_data.get();}

private:
    std::shared_ptr<SomeStruct> m_data;
};
Piotr Barejko
  • 456
  • 2
  • 12