Search code examples
c++raiinats.io

RAII when using ascyc c library


I have an RAII wrapper around the cnats library. We don't need to understand the details of cnats. There are just two important piecies. cnats is the C client for communicating with a NATS message broker. As is usually the case, they have custom creation and deletion functions for all of their structures.

I have an RAII wrapper for creating a natsMsg object. The constructor:

NatsMessage(const std::string& subject, const std::string& data) {
        natsMsg* temp;
        HANDLE_STATUS(natsMsg_Create(&temp, subject.c_str(), nullptr, data.c_str(), data.size()));
        msg = std::shared_ptr<natsMsg>(temp, [](natsMsg* p) {natsMsg_Destroy(p);});
    }

There are two publish functions: js_PublishMsg and js_PublishMsgAsync. The regular function just publishes the message. I am presently adding support for the Async version which takes over the message and calls the natsMsg_Destroy when it is done causing a race condition and a double free error.

From reading about std::shared_ptr:

The ownership of an object can only be shared with another shared_ptr by copy constructing or copy assigning its value to another shared_ptr.

There is similar language for std::unique_ptr.

In essence my problem is that I would like to use a smart_pointer with a custom deleter. When I call the Async publish function, I would like to somehow "remove" ownership so that the c library handles the deletion.

I'm okay switching to a unique_ptr or other smart pointer (or some other way of accomplishing this).


Solution

  • if you don't need shared ownership, you can use std::unique_ptr which has release() function

    struct NatsMessage{
        NatsMessage() {
            natsMsg* temp;
            GetMsg(&temp);
            msg.reset(temp);
    
            // when you need to transfer ownership to C library
            // natsMsg* m = msg.release();
        }
    
        std::unique_ptr<natsMsg,decltype(&natsMsg_Destroy)> msg = {nullptr, &natsMsg_Destroy};
    };
    

    • if you want to keep using std::shared_ptr, you can set some flag to disable the deleter.

    • also you can use raw pointer and manually release it in destructor, note this also require correctly handle copy/move case.