CiaranWelsh
CiaranWelsh

Reputation: 7679

How to generalize this C++ wrapper around a C 'class'?

I am writing a C++ wrapper around a C library. Here is an example of my strategy.

    // header file
    class LibrdfUri { // wrapper around librdf.h librdf_uri* 

        /*
         * If the deleter of std::unique_ptr is an empty
         * class then it can do some optimizations and
         * not actually store the deleter object.
         * Otherwise it has to accommodate extra space for
         * the deleter, which is unnecessary
         *  https://stackoverflow.com/questions/61969200/what-is-the-purpose-of-wrapping-this-private-deleter-function-in-a-struct/61969274#61969274
         */
        struct deleter {
            // turns deleter into a functor. For passing on to unique_ptr
            void operator()(librdf_uri *ptr);
        };
        // automate management of librdf_uri* lifetime
        std::unique_ptr<librdf_uri, deleter> librdf_uri_;

    public:
        LibrdfUri() = default;

        explicit LibrdfUri(const std::string& uri); // construct from string

        librdf_uri *get(); // returns the underlying raw pointer

    };
    // implementation
    void LibrdfUri::deleter::operator()(librdf_uri *ptr) {
        librdf_free_uri(ptr); // this is the C library function for destruction of librdf_uri
    }

    LibrdfUri::LibrdfUri(const std::string &uri) {
        // create pointer to underlying C library 'object'
        librdf_uri_ = std::unique_ptr<librdf_uri, deleter>(
                librdf_new_uri(World::getWorld(), (const unsigned char *) uri.c_str()) // World::getWorld is static. Returns a pointer required by librdf_new_uri
        );
    }

    librdf_uri *LibrdfUri::get() {
        return librdf_uri_.get();
    }

// and is used like so:
LibrdfUri uri("http://uri.com");
librdf_uri* curi = uri.get(); // when needed

This works for the single type librdf_uri* which is a part of the underlying library however I have lots of these. My question is double barrelled. The first part concerns the best general strategy for generalizing this wrapper to other classes while the second is concerns the implementation of that strategy.

Regarding the first part, here are my thoughts: 1. I could implement each class manually like I've done here. This is probably the simplest and least elegant. Yet it still might be my best option. However there is a small amount of code duplication involved, since each CWrapper I write will essentially have the same structure. Not to mention if I need to change something then I'll have to do each class individually. 2. Use an base class (abstract?) 3. Use a template

The second part of my question is basically: if I implement either option 2 or 3 (which I think might even be just a single option) how would I do it?

Here is a (vastly broken) version of what I'm thinking:

template<class LibrdfType>
class CWrapper {

    struct deleter { ; //?
        void operator()(LibrdfType *ptr) {
            // ??
        };
    }

    std::unique_ptr<LibrdfType, deleter> ptr;

public:
    CWrapper() = default;

    LibrdfType *get() {
        ptr.get();
    };

};

Then, LibrdfUri and any other C class I need to wrap, would just subclass CWrapper

Upvotes: 1

Views: 87

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275565

This is a better deleter:

template<auto f>
using deleter=std::integral_constant< std::decay_t<decltype(f)>, f >;

use:

deleter<librdf_free_uri>

is a stateless deleter that calls librdf_free_uri.

But we don't need that I think. Here is what I might do:

There are 3 pieces of information you need.

  • How to construct
  • How to destroy
  • What type to store

One way is to define ADL baser helpers with famous names that you override to delete/construct.

template<class T>struct tag_t{};
template<class T>constexpr tag_t<T> tag{};

template<class T>
void delete_wrapptr(T*)=delete;
struct cleanup_wrapptr{
  template<class T>
  void operator()(T* t)const{ delete_wrapptr(t); }
};
template<class T>
using wrapptr=std::unique_ptr<T, cleanup_wrapptr>;
template<class T>
wrapptr<T> make_wrapptr( tag_t<T>, ... )=delete;

now you just have to write overloads for make and delete.

void delete_wrapptr(librdf_uri* ptr){
    librdf_free_uri(ptr); // this is the C library function for destruction of librdf_uri
}

librdr_uri* make_wrapptr(tag_t<librdf_uri>, const std::string &uri) {
    return librdf_new_uri(World::getWorld(), (const unsigned char *) uri.c_str()); // World::getWorld is static. Returns a pointer required by librdf_new_uri
 }

and you can;

wrapptr<librdf_uri> ptr = make_wrapptr(tag<librdf_uri>, uri);

the implementation becomes just overriding those two functions.

make_wrapptr and delete_wrapptr overloads you write need to be visible at creating point, and in the namespace of T, tag_t or cleanup_wrapptr. The implementations can be hidden in a cpp file, but the declaration of the overload cannot.

Upvotes: 1

Related Questions