I'm trying to use a C library which basically only exposes typedefs
to structs that are used internally. The issue is that I want to use smart pointers to manage the lifetime of the raw pointers which are the interface to the library, but I cannot create the smart pointers because of an incomplete_type
error. Note I have asked a previous question previous question trying to solve the same problem but the question turned out to be a poor representation of my actualy problem:
I cannot figure out how to use a smart pointer to the type that I need from the C library. Instead I've been using smart pointers to the underlying raw pointers (see below) but this isn't exactly what I'd like, and probably not ideal.
Here is some code:
#include "librdf.h" // the library I need to use
int main() {
// call necessary to use the librdf library.
librdf_world *world = librdf_new_world();
librdf_world_open(world);
/*
* Error: In instantiation of function template specialization
* 'std::make_unique<librdf_world_s, librdf_world_s *&>'
* allocation of incomplete type 'librdf_world_s'
*/
std::unique_ptr<librdf_world> w1 = std::make_unique<librdf_world>(world);
/* // errors:
* 1) Template argument for template type parameter must be a type // (on librdf_free_world left)
*
* 2) No matching function for call to 'make_unique' // (on std::make_unique<librdf_world, librdf_free_world>, right)
*
*/
std::unique_ptr<librdf_world, librdf_free_world> w2 = std::make_unique<librdf_world, librdf_free_world>(world);
/* Error:
* In instantiation of template class 'std::unique_ptr<librdf_world_s, void (librdf_world_s *)>'
* data member instantiated with function type 'void (librdf_world_s *)' // (on w3)
*
* No matching function for call to 'make_unique' candidate function template not viable:
* cannot convert argument of incomplete type 'librdf_world *'
* (aka 'librdf_world_s *') to 'void (&&)(librdf_world_s *)' for 1st argument // (on std::make_unique<librdf_world, decltype(librdf_free_world)>)
*/
std::unique_ptr<librdf_world, decltype(librdf_free_world)> w3 = std::make_unique<librdf_world, decltype(librdf_free_world)>(world);
/* No error:
* This version actually works and has been my strategy for a while now. Note,
* I've been using shared_ptr because I need other objects to have a reference
* to the `world` to create other objects from the library. An example call to the library would be:
* librdf_new_node(world, ... other arguments ... );
* However using a smart pointer to a raw pointer doesn't solve the problem of automatically
* managing the lifetime of the underlying raw pointer (according to valgrind). My attempt at
* overcoming this issue is to wrap the smart pointer in a class and have the shared_ptr as
* the single private variable. Then, in the destructor, when the shared_ptr::use_count gets to 1
* I call the C destructor.
* i.e.
* ~LibrdfWorld(){ // wrapper class name
* if (world_.use_count == 1){ // world_ is private instance of shared pointer to librdf_world*
* librdf_free_world(*world_.getWorld()); // call the c library destructor when ref count gets to 1
* }
*
*/
std::shared_ptr<librdf_world*> w4 = std::make_shared<librdf_world*>(world);
}
So what is the best way to use this library from c++? Do I use the raw pointers and manage the life times manually? Do I use the smart pointer to the raw pointer? Or is there another way I haven't thought of.
std::make_shared
and std::make_unique
are used to allocate and then construct objects and return them as shared_
or unique_
pointers. It expects to receive arguments which will be passed to the constructor of your class, and it needs the type to be complete so that it can call this constructor.
In this case, however, the library allocates and constructs the object for you with librdf_new_world()
, and you don't even want the behavior of std::make_unique
or std::make_shared
. Simply pass world
into the constructor of your smart pointer variable like so
#include "librdf.h"
#include <memory>
int main() {
// call necessary to use the librdf library.
librdf_world *world = librdf_new_world();
librdf_world_open(world);
// shared pointer
std::shared_ptr<librdf_world> w2(world, librdf_free_world);
}
It's probably best to just do it all on one line so your smart pointer is the only reference to the object, that way you don't have to worry about accessing the data after its freed or double freeing it.
std::shared_ptr<librdf_world> world(librdf_new_world(), librdf_free_world);
librdf_world_open(world.get());
To get this to work with std::unique_ptr
, you need to employ the strategy used in the answer to your previous post, making a default-constructible deleter class and passing that as a template argument to your std::unique_ptr
.
Note, in C++20 mode, you can make a lambda as a deleter inline to accomplish this as well.
using WorldPtr = std::unique_ptr<
librdf_world,
decltype([](librdf_world* w){librdf_free_world(w);})
>;
WorldPtr world(librdf_make_world);
librdf_world_open(world.get());