I have written an extensible c++ wrapper around a very hard to use but also very useful c library. The goal is to have the convience of c++ for allocating the object, exposing its properties, deallocating the object, copy semantics etc...
The problem is this: sometimes the c library wants the underlying object (a pointer to the object), and the class destructor should not destroy the underlying memory. While most of the time, the destructor should deallocate the underlying object. I have experimented with setting a bool hasOwnership
flag in the class so that the destructor, assignment operator, etc... will know whether or not it should free the underlying memory or not. However, this is cumbersome for the user, and also, sometimes there is no way to know when another process will be using that memory.
Currently, I have it setup where when the assignment comes from a pointer of the same type as the underlying type, then I set the hasOwnership flag. I do the same when the overloaded constructor is called using the pointer from the c library. Yet, this still does not handle the case when the user has created the object and passed it to one of my functions which calls the c_api and the library stores the pointer for later use. If they were to delete their object, then it would no doubt cause a segfault in the c library.
Is there a design pattern that would simplify this process? Maybe some sort of reference counting?
4 Answers 4
If the responsibility for cleaning up dynamically allocated stuff shifts between your wrapper class and the C library based on how things get used, you are either dealing with a badly designed C library, or you are trying to do too much in your wrapper class.
In the first case, all you can do is keep track of who is responsible for the cleanup and hope no mistakes are made (either by you or by the maintainers of the C library).
In the second case, you should rethink your wrapper design. Does all the functionality belong together in the same class, or can it be split into multiple classes. Perhaps the C library uses something similar to the Facade design pattern and you should keep a similar structure in your C++ wrapper.
In any case, even if the C library is responsible for cleaning up some stuff, there is nothing wrong with keeping a reference/pointer to that stuff. You just need to remember that you are not responsible for cleaning up what the pointer refers to.
-
What if I ensured a clone was made of the underlying object when it is passed to the lib. Then I can free my memory as I please and it wont effect anything. However, then I wont receive changes to the object back in my class.Jonathan Henson– Jonathan Henson2013年01月05日 17:52:23 +00:00Commented Jan 5, 2013 at 17:52
-
And yes, some very bad practices were used in this lib. For instance: instead of having the user pass a const char* to a function that sets a string in a structure then making a copy to store in the structure, they expect the user to allocate the string on the fly and pass a char* that it just stores without making a copy. That is one of the main reasons I made a wrapper--to ensure that proper copy semantics were followed.Jonathan Henson– Jonathan Henson2013年01月05日 17:55:57 +00:00Commented Jan 5, 2013 at 17:55
-
@JonathanHenson: Letting the client allocate something and pass ownership/responsibility of it to the library is a common optimisation technique to avoid excess copying and entirely valid for C libraries (as long as it is done consistently). But it indeed means that, when used from C++, the user may need to make additional copies to conform to the interface requirements of the C library.Bart van Ingen Schenau– Bart van Ingen Schenau2013年01月06日 08:31:15 +00:00Commented Jan 6, 2013 at 8:31
Often you can use a pattern like this:
class C {
public:
void foo() {
underlying_foo(handle.get());
}
void bar() {
// transfers ownership
underlying_bar(handle.release());
}
// use default copy/move constructor and assignment operator
private:
struct deleter {
void operator()(T* ptr) {
deleter_fn(ptr);
}
};
std::unique_ptr<T, deleter> handle;
};
By using release
you can explicitly transfer ownership. However this is confusing and you should avoid it if at all possible.
Most C libraries have a C++-like object life cycle (object allocation, accessors, destruction) that maps nicely onto the C++ pattern without ownership transfer.
If users need shared ownership, they should use shared_ptr
with your classes. Don't try to implement any ownership sharing yourself.
Update: If you want to make the transfer of ownership more explicit, you can use a reference qualifier:
void bar() && { ... }
Then users must call bar
on lvalues like this:
C o;
std::move(o).bar(); // transfer of ownership is explicit at call site
-
@Phillip, that looks like exactly what I need. I'll try this and get back with you.Jonathan Henson– Jonathan Henson2013年01月05日 03:57:44 +00:00Commented Jan 5, 2013 at 3:57
-
One question. What exactly IS ownership in this case. Does it mean the privilege to access/modify the object, or just the ability to construct and destruct the object?Jonathan Henson– Jonathan Henson2013年01月05日 04:02:16 +00:00Commented Jan 5, 2013 at 4:02
-
basically, once I pass the underlying memory to the c-lib, I am not supposed to ever make a free call to the memory. Though, I can and often will have to access the data in the object.Jonathan Henson– Jonathan Henson2013年01月05日 04:06:32 +00:00Commented Jan 5, 2013 at 4:06
-
2Ownership is the right to destroy the object.DeadMG– DeadMG2013年01月05日 10:52:49 +00:00Commented Jan 5, 2013 at 10:52
-
@JonathanHenson: that's an interesting case that's not covered right now in this idiom: once you have released a
unique_ptr
, it doesn't store the pointer it held any more. Modeling transfer of ownership and at the same time allowing access to disowned resources seems to be quite confusing for users. How do you control when the C library frees the objects it now owns?Philipp– Philipp2013年01月05日 21:59:44 +00:00Commented Jan 5, 2013 at 21:59
There is a straightforward answer to your issue, smart pointers. By using a smart pointer to hold the C library's memory and adding a reference when the pointer is also in the library (and dropping the reference when the C library returns) then you will automatically free the memory when the reference count drops to zero (and only then)
-
1This does not address the problem, unless I just badly misunderstand smart pointers. The problem is that sometimes, my destructor should clean up the memory, other times the c-library cleans up the memory. These structures have special init, free functions. The library may call free on its own, before the reference count of the smart-pointer hits 0. I need to basically decide ownership and let it go. I don't need a smart pointer to know when to clean up the memory, the problem is that sometimes, I am not the one responsible for cleaning it up and I need to not free it in the destructor--ever.Jonathan Henson– Jonathan Henson2013年01月05日 03:10:50 +00:00Commented Jan 5, 2013 at 3:10
-
Yep, I agree, I misunderstood the issue. Just one observation, do the different libraries share the same memory allocation table? My intense C/C++ stuff was 10 years ago, and on Visual Studio back then, allocating memory in one library and freeing it in another library would be a memory leak, unless you changed some compiler options for BOTH libraries. Thye consequence of doing this would then be memory management contension on highly threaded applications. Its quite likely this is obsolete info though.Michael Shaw– Michael Shaw2013年01月09日 22:17:26 +00:00Commented Jan 9, 2013 at 22:17
-
not if everything is loaded into the same address space, and it is. Anyways, I am recompiling the c-lib in the same assembly as my wrapper.Jonathan Henson– Jonathan Henson2013年01月09日 22:56:05 +00:00Commented Jan 9, 2013 at 22:56
If the library can free things internally, and the scenarios where this can happen is well documented, then all you can do is set a flag like you're doing already.
-
basically, once I pass the underlying memory to the c-lib, I am not supposed to ever make a free call to the memory. Though, I can and often will have to access the data in the object.Jonathan Henson– Jonathan Henson2013年01月05日 04:12:11 +00:00Commented Jan 5, 2013 at 4:12
Explore related questions
See similar questions with these tags.
unique_ptr
can in most cases already handle this type of resources, so you don't need to implement the resource management yourself.unique_ptr
uses therelease
method to give up ownership of the stored object.