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
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.
Related
For educational sake I am trying to understand a bit how smart pointers works with custom deleters. So I've written this code on my own that simulates how std::uniqe_ptr works. In fact I also think about changing the deleters template functions into template function classes and specialize one for dynamic arrays. Anyway this is what I've tried:
template <typename T>
void Free(T* p)
{
std::cout << "Free(T*): Freeing memory of a dynamic object...\n";
delete p;
}
template <typename T>
void Del_It(T* p)
{
std::cout << "Del_It(T*): freeing memory of a dynamic object...\n";
delete p;
}
template <typename T, typename Deleter=void(*)(T*)>
class Uniptr
{
public:
Uniptr(T* = nullptr, Deleter = Free<T>);
virtual ~Uniptr();
private:
T* ptr_{nullptr};
Deleter delPtr_{nullptr};
};
template <typename T, typename Deleter>
Uniptr<T, Deleter>::Uniptr(T* p, Deleter d) :
ptr_(p),
delPtr_(d)
{
}
template <typename T, typename Deleter>
Uniptr<T, Deleter>::~Uniptr()
{
delPtr_(ptr_);
delPtr_ = nullptr;
}
int main()
{
Uniptr<int, void(*)(int*)> upi(new int(9), Del_It<int>);
std::cout << "\ndone\n";
}
It looks that it works fine but I need your advice, tips... Thank you
To be honest custom deleter can be everything. Let's make a dirty our hand,
int main()
{
auto f = [](FILE* f){std::fclose(f); std::cout<<"I am closing file\n";};
std::unique_ptr<FILE,decltype(f)>fptr{std::fopen("test.txt","w"), f};
}
What we have done here? We have wrapped the file handling in C via unique_ptr.
What we get it? In case of forget to close the file after the operation, we ensure that it will be closed automatically.
So that, do not think that custom deleter can only use to delete pointer. I can be specified for any application such as a socket, file, etc...
Please check that unique_ptr
Code was taken from File Wrapper
A custom deleter can used to perform some custom clean up operation when the unique pointer is destroyed. In your case you're not doing anything apart from deleting the pointer. Therefore you're not performing any custom clean up. Plus you've got two functions Free and Del_It that do the same thing. If Del_It is meant for dynamically-allocated arrays, it should do delete [] p.
Suppose you're working with legacy code that specifically requires you to call a release() function that frees resources:
class LegacyClass
{
...
void release() {...}
~LegacyClass() {...}
};
You can't change the legacy code (it might even be in shared library), but you have to call the release() function. So you could define a custom deleter to do that:
struct Deleter
{
void operator()(LegacyClass *ptr) const
{
if (ptr)
{
ptr->release();
delete ptr;
}
}
};
With a unique pointer the deleter is actually part of the type:
unique_ptr<T, Deleter>
but for a shared pointer the custom deleter is not part of the type; it's passed into the constructor. The custom deleter can be defined as:
functor
function pointer
lambda
std::function *
*(this one can bloat the size of your unique pointer)
In normal C++ design, most objects can be deleted either by a delete statement, the free function, or a library-specific equivalent to free. For such objects, the unique_ptr Deleter implementation can be a stateless object that is eliminated through Empty Base Class Optimization. However, some libraries require using another object (which might contain a function pointer or some other context) to delete objects from that library.
typedef struct lib_object lib_object;
struct lib_api {
lib_object (*createInstance)();
void (*freeInstance)(lib_object *o);
};
One could wrap this in unique_ptr by storing a lib_api pointer as a data member in a custom Deleter, but if multiple lib_object instances needed to be managed, e.g. in a container, it would double the memory overhead of tracking the objects. What kind of pattern can be used to maintain RAII principles when dealing with this library, while still remaining memory efficient?
If there is only ever one lib_api object, then you can have your deleter get a static pointer to it.
If there can be more than one lib_api object then you have no choice but to store a pointer to it in the Deleter.
I use a custom deleter template for such objects.
template<typename T, T Function>
struct function_deleter
{
template<typename U>
auto operator()(U&& u) const noexcept(noexcept(Function(std::forward<U>(u))))
{
return Function(std::forward<U>(u));
}
};
then you can have use your deleter call free:
unique_ptr<int, function_deleter<void(*)(void*), &free>> uniq;
And its size is still equal to one pointer. live demo
Come C++17 you'll be able to use auto for non-type template parameters, simplifying the code to:
template<auto Function>
struct function_deleter
{
template<typename U>
auto operator()(U&& u) const noexcept(noexcept(Function(std::forward<U>(u))))
{
return Function(std::forward<U>(u));
}
};
and
unique_ptr<int, function_deleter<&call_free>> uniq;
live demo
Having this, in your case I'd keep a unique_ptr<pair<lib_object, lib_api>> with static deleter supporting this structure.
using lib_pair = pair<lib_object, lib_api>;
void lib_free(lib_pair* p){
p->second.freeInstance(p->first);
delete p;
}
using unique_lib_ptr = unique_ptr<lib_pair, function_deleter<void(*)(lib_pair*), &lib_free>>
This seems more cache-heavy but might just be your thing.
There should exist a more elegant solution but I would write something like
template <class ContainerType>
class TObjectContainer : public ContainerType {
public:
TObjectContainer() = default;
TObjectContainer(const TObjectContainer&); // should call createCopy
TObjectContainer(TObjectContainer&&) = default;
~TObjectContainer()
{ for (lib_object* element : *this)
(*freeInstance)(element);
}
private:
void (*freeInstance)(lib_object *o);
};
typedef TObjectContainer<std::vector<lib_object*>> ObjectVector;
It does not use unique_ptr but it should basically do the job.
Note that your are likely to overload every removal method like clear to call freeInstance or pop_back to return your original std::unique_ptr.
I tried to create packed array as a data structure for a game engine as described here:-
http://experilous.com/1/blog/post/dense-dynamic-arrays-with-stable-handles-part-1
In short, the structure stores values, instead of pointers.
Here is a draft.
template<class T> class Id{
int id;
}
template<class T> class PackArray{
std::vector <int>indirection ; //promote indirection here
std::vector <T>data;
//... some fields for pooling (for recycling instance of T)
Id<T> create(){
data.push_back(T());
//.... update indirection ...
return Id( .... index , usually = indirection.size()-1 .... )
}
T* get(Id<T> id){
return &data[indirection[id.id]];
//the return result is not stable, caller can't hold it very long
}
//... others function e.g. destroy(Id<T>) ...
}
The prototype works as I wished, but now I concern the beauty of old code.
For example, I had always created a new object like this:-
Bullet* bullet = new Bullet(gameEngine,velocity);
Now I must call :-
Id<Bullet> bullet = getManager()->create()->ini(velocity);
// getManager() usually return PackArray<Bullet>*
// For this data structure,
// if I want to hold the object for a long time, I have to cache it as Id.
Here are the questions :-
The new version of code is more ugly.
Should I avoid it? How to avoid it?
How to avoid / reduce programmer's-work of the above modification?
It is very tedious, when there are many of them scattering around.
(Edit) The scariest part is change in the type declaration e.g.
class Rocket{
std::vector<Bullet*> bullets;
//-> std::vector<Id<Bullet>> bullets;
void somefunction(){
Bullet* bullet = someQuery();
//-> Id<Bullet> bullet
}
}//These changes scatter around many places in many files.
This change (inserting the word "Id<>") means that the game logic has to know the underlying data structure that used to store Bullet.
If the underlying data structure would be changed again in future, I will have to manually refactor them one by one again (from Id<> to something else), i.e. lower maintainability.
(optional) What is the name of this data structure / technique?
As a library, should Id has a field of PackArray* to enable accessing the underlying object (e.g. Bullet*), without manager()?
Bullet* bullet = someId->getUnderlyingObject();
This behaviour of id sounds like handles, as in you don't give out information about the storage method, but guarantee access as long as the handle is valid. In the later respect handles behave like raw pointers: you won't be able to tell if it's valid (at least without the manager) and the handle might be reused at some point.
The question if changing from raw pointers to handles produces uglier code is very opinionated and I'd rather keep this objective: there's a balance between readably explicit and too much typing - everyone draws their own limits here. There's also advantages to having the calling site specify getManager: maybe there are multiple possible instances of these managers, maybe getting the manager requires locking and for multiple operations you want to lock only once. (You can support both of these cases in addition to what I present below.)
Let's use pointer/iterator notation to access the objects through our handles, reducing the amount of code changes necessary. Using std::make_unique and std::make_shared for reference, let's define make_handle to dispatch the creation to the right manager. I've adjusted PackArray::create a bit to make the following example more compact:
template<class T> class Handle;
template<class T> class PackArray;
template<class T, class... Args> Handle<T> make_handle(Args&&... args);
template<class T>
struct details {
friend class Handle<T>;
template<class U, class... Args> friend Handle<U> make_handle(Args&&... args);
private:
// tight control over who get's to access the underlying storage
static PackArray<T>& getManager();
};
template<class T>
class Handle {
friend class PackArray<T>;
size_t id;
public:
// accessors (via the manager)
T& operator*();
T* operator->() { return &*(*this); }
};
template<class T>
class PackArray {
std::vector<size_t> idx;
std::vector<T> data;
public:
template<class... Args>
Handle<T> create(Args&&... args) {
Handle<T> handle;
handle.id = data.size();
idx.push_back(data.size());
// enables non-default constructable types
data.emplace_back(std::forward<Args>(args)...);
return handle;
}
// access using the handle
T& get(Handle<T> handle) {
return data[idx[handle.id]];
}
};
template<class T, class... Args>
Handle<T> make_handle(Args&&... args) {
Handle<T> handle = details<T>::getManager().create(std::forward<Args>(args)...);
return handle;
}
template<class T>
T& Handle<T>::operator*() {
return details<T>::getManager().get(*this);
}
And the usage code would look like:
Handle<int> hIntA = make_handle<int>();
Handle<int> hIntB = make_handle<int>(13);
Handle<float> hFloatA = make_handle<float>(13.37f);
Handle<Bullet> hBulletA = make_handle<Bullet>();
// Accesses through the respective managers
*hIntA = 42; // assignment
std::cout << *hIntB; // prints 13
float foo = (*hFloatA + 12.26f) * 0.01;
applyDamage(hBulletA->GetDmgValue());
Every type needs a manager, i.e. if you don't define a default you'll get a compiler error. Alternatively you can provide a generic implementation (note: the initialisation of instance is not thread safe!):
template<class T>
PackArray<T>& details<T>::getManager() {
static PackArray<T> instance;
return instance;
}
You get special behaviour via template specialisation. You can even replace the manager type via template specialisation, allowing you to easily compare storage strategies (e.g. SOA vs. AOS).
template<>
struct details<Bullet> {
friend class Handle<Bullet>;
template<class U, class... Args> friend Handle<U> make_handle(Args&&... args);
private:
static MyBulletManager& getManager() {
static MyBulletManager instance;
std::cout << "special bullet store" << std::endl;
return instance;
}
};
And you can even make all of this const-correct (the same techniques as implementing custom iterators apply).
You may even want to extend the details<T> to a full traits type... It's all a balance between generalisation and complexity.
I am creating a class which interops with some Windows API code, now one of the pointers I have to initialize is done by calling a native function which initializes it.
My pointers are of type std::unique_ptr with a custom deleter, which calls the WinAPI deleter function provided, however I cannot pass the unique_ptr with the & address-of operator to the init-function. Why?
I have created a sample that demonstrates my problem:
#include <memory>
struct foo
{
int x;
};
struct custom_deleter {};
void init_foo(foo** init)
{
*init = new foo();
}
int main()
{
std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&foo_ptr);
}
The compiler barks and says:
source.cpp: In function 'int main()':
source.cpp:19:21: error: cannot convert 'std::unique_ptr<foo, custom_deleter>*' to 'foo**' for argument '1' to 'void init_foo(foo**)'
Somewhere under the covers, unique_ptr<foo> has a data member of type foo*.
However, it's not legitimate for a user of the class to directly modify that data member. Doing so would not necessarily preserve the class invariants of unique_ptr, in particular it wouldn't free the old pointer value (if any). In your special case you don't need that to happen, because the previous value is 0, but in general it should happen.
For that reason unique_ptr doesn't provide access to the data member, only to a copy of its value (via get() and operator->). You can't get a foo** out of your unique_ptr.
You could instead write:
foo *tmp;
init_foo(&tmp);
std::unique_ptr<foo, custom_deleter> foo_ptr(tmp);
This is exception-safe for the same reason that std::unique_ptr<foo, custom_deleter> foo_ptr(new foo()); is exception-safe: unique_ptr guarantees that whatever you pass in to its constructor will eventually get deleted using the deleter.
Btw, doesn't custom_deleter need an operator()(foo*)? Or have I missed something?
Steve has already explained what the technical problem is, however, the underlying problem goes much deeper: The code employs an idiom helpful when you deal with naked pointers. Why does this code do two-step initialization (first create the object, then initialize it) in the first place? Since you want to use smart pointers, I'd suggest you carefully adapt the code:
foo* init_foo()
{
return new foo();
}
int main()
{
std::unique_ptr<foo, custom_deleter> foo_ptr( init_foo() );
}
Of course, renaming init_foo() to create_foo() and having it return a std::unique_ptr<foo> directly would be better. Also, when you use two-step initialization, it's often advisable to consider using a class to wrap the data.
You can use the following trick:
template<class T>
class ptr_setter
{
public:
ptr_setter(T& Ptr): m_Ptr{Ptr} {}
~ptr_setter() { m_Ptr.reset(m_RawPtr); }
ptr_setter(const ptr_setter&) = delete;
ptr_setter& operator=(const ptr_setter&) = delete;
auto operator&() { return &m_RawPtr; }
private:
T& m_Ptr;
typename T::pointer m_RawPtr{};
};
// Macro will not be needed with C++17 class template deduction.
// If you dislike macros (as all normal people should)
// it's possible to replace it with a helper function,
// although this would make the code a little more complex.
#define ptr_setter(ptr) ptr_setter<decltype(ptr)>(ptr)
and then:
std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&ptr_setter(foo_ptr));
I eventually came up with an approach that allows to initialise unique_ptr's with a code like this:
struct TOpenSSLDeleter { ... }; // Your custom deleter
std::unique_ptr<EVP_MD_CTX, TOpenSSLDeleter> Ctx;
...
Ctx = MakeUnique(EVP_MD_CTX_create()); // MakeUnique() accepts raw pointer
And here is the solution:
template <class X>
struct TUniquePtrInitHelper {
TUniquePtrInitHelper(X *Raw) noexcept {
m_Raw = Raw;
}
template <class T, class D>
operator std::unique_ptr<T, D>() const noexcept {
return std::unique_ptr<T, D>(m_Raw);
}
private:
X *m_Raw;
};
template <class X>
TUniquePtrInitHelper<X> MakeUnique(X *Raw) noexcept {
return {Raw};
}
I have an shared object that need to be send to a system API and extract it back later. The system API receives void * only. I cannot use shared_ptr::get() because it do not increases the reference count and it could be released by other threads before extract from the system API. Sending a newed shared_ptr * will work but involves additional heap allocation.
One way to do it is to let the object derived from enable_shared_from_this. However, because this class template owns only a weak_ptr, it is not enough to keep the object from released.
So my solution looks like the following:
class MyClass:public enable_shared_from_this<MyClass> {
private:
shared_ptr<MyClass> m_this;
public:
void *lock(){
m_this=shared_from_this();
return this;
}
static shared_ptr<MyClass> unlock(void *p){
auto pthis = static_cast<MyClass *>(p);
return move(pthis->m_this);
}
/* ... */
}
/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj->lock());
/* ... */
auto punlocked = MyClass::unlock(system_api_reveive_obj());
Are there any easier way to do this?
The downside of this solution:
it requires an additional shared_ptr<MyClass> in the MyClass object layout, in addition of a weak_ptr in the base class enable_shared_from_this.
As I mentioned in the comments, access to lock() and unlock() concurrently is NOT Safe.
The worst thing is that this solution can only support lock() once before a call of unlock(). If the same object is to be use for multiple system API calls, additional reference counting must be implemented.
If we have another enable_lockable_shared_from_this class it will be greate:
class MyClass:public enable_lockable_shared_from_this<MyClass> {
/* ... */
}
/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj.lock());
/* ... */
auto punlocked = unlock_shared<MyClass>(system_api_reveive_obj());
And the implementation of enable_lockable_shared_from_this is similar as enable_shared_from_this, the only difference is it implements lock() and a helper function unlock_shared. The calling of those functions only explicitly increase and decrease use_count(). This will be the ideal solution because:
It eliminate the additional space cost
It reuses the facilities existing for shared_ptr to guarantee concurrency safety.
The best thing of this solution is that it supports multiple lock() calls seamlessly.
However, the only biggest downside is: it is not available at the moment!
UPDATE:
At least two answers to this question involves a container of pointers. Please compare those solutions with the following:
class MyClass:public enable_shared_from_this<MyClass> {
private:
shared_ptr<MyClass> m_this;
mutex this_lock; //not necessory for single threading environment
int lock_count;
public:
void *lock(){
lock_guard lck(this_lock); //not necessory for single threading environment
if(!lock_count==0)
m_this=shared_from_this();
return this;
}
static shared_ptr<MyClass> unlock(void *p){
lock_guard lck(this_lock); //not necessory for single threading environment
auto pthis = static_cast<MyClass *>(p);
if(--lock_count>0)
return pthis->m_this;
else {
lock_count=0;
return move(pthis->m_this); //returns nullptr if not previously locked
}
}
/* ... */
}
/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj->lock());
/* ... */
auto punlocked = MyClass::unlock(system_api_reveive_obj());
This is absolutely an O(1) vs O(n) (space; time is O(log(n)) or similar, but absolutely greater than O(1) ) game.
I am now have an idea of the following:
template<typename T>
struct locker_helper{
typedef shared_ptr<T> p_t;
typedef typename aligned_storage<sizeof(p_t), alignment_of<p_t>::value>::type s_t;
};
template<typename T> void lock_shared(const shared_ptr<T> &p){
typename locker_helper<T>::s_t value;
new (&value)shared_ptr<T>(p);
}
template<typename T> void unlock_shared(const shared_ptr<T> &p){
typename locker_helper<T>::s_t value
= *reinterpret_cast<const typename locker_helper<T>::s_t *const>(&p);
reinterpret_cast<shared_ptr<T> *>(&value)->~shared_ptr<T>();
}
template<typename T>
void print_use_count(string s, const shared_ptr<T> &p){
cout<<s<<p.use_count()<<endl;
}
int main(int argc, char **argv){
auto pi = make_shared<int>(10);
auto s = "pi use_count()=";
print_use_count(s, pi); //pi use_count()=1
lock_shared(pi);
print_use_count(s, pi);//pi use_count()=2
unlock_shared(pi);
print_use_count(s, pi);//pi use_count()=1
}
and then we can implement the original example as following:
class MyClass:public enable_shared_from_this { /*...*/ };
/* ... */
auto pobj = make_shared<MyClass>(...);
/* ... */
lock_shared(pobj);
system_api_send_obj(pobj.get());
/* ... */
auto preceived =
static_cast<MyClass *>(system_api_reveive_obj())->shared_from_this();
unlock_shared(preceived);
It is easy to implement a enable_lockable_shared_from_this with this idea. However, the above is more generic, allows control things that is not derived from enable_lockable_from_this` template class.
By adjusting the previous answer, I finally get the following solution:
//A wrapper class allowing you to control the object lifetime
//explicitly.
//
template<typename T> class life_manager{
public:
//Prevent polymorphic types for object slicing issue.
//To use with polymorphic class, you need to create
//a container type for storage, and then use that type.
static_assert(!std::is_polymorphic<T>::value,
"Use on polymorphic types is prohibited.");
//Example for support of single variable constructor
//Can be extented to support any number of parameters
//by using varidict template.
template<typename V> static void ReConstruct(const T &p, V &&v){
new (const_cast<T *>(&p))T(std::forward<V>(v));
}
static void RawCopy(T &target, const T &source){
*internal_cast(&target) = *internal_cast(&source);
}
private:
//The standard said that reterinterpret_cast<T *>(p) is the same as
//static_cast<T *>(static_cast<void *>(p)) only when T has standard layout.
//
//Unfortunately shared_ptr do not.
static struct { char _unnamed[sizeof(T)]; } *internal_cast(const T *p){
typedef decltype(internal_cast(nullptr)) raw;
return static_cast<raw>(static_cast<void *>(const_cast<T *>(p)));
}
};
//Construct a new instance of shared_ptr will increase the reference
//count. The original pointer was overridden, so its destructor will
//not run, which keeps the increased reference count after the call.
template<typename T> void lock_shared(const std::shared_ptr<T> &p){
life_manager<shared_ptr<T> >::ReConstruct(p, std::shared_ptr<T>(p));
}
//RawCopy do bit-by-bit copying, bypassing the copy constructor
//so the reference count will not increase. This function copies
//the shared_ptr to a temporary, and so it will be destructed and
//have the reference count decreased after the call.
template<typename T> void unlock_shared(const std::shared_ptr<T> &p){
life_manager<shared_ptr<T> >::RawCopy(std::shared_ptr<T>(), p);
}
This is actually the same as my previous version, however. The only thing I did is to create a more generic solution to control object lifetime explicitly.
According to the standard(5.2.9.13), the static_cast sequence is definitely well defined. Furthermore, the "raw" copy behavior could be undefined but we are explicitly requesting to do so, so the user MUST check the system compatibility before using this facility.
Furthermore, actually only the RawCopy() are required in this example. The introduce of ReConstruct is just for general purpose.
Why not just wrap the void * API in something that tracks the lifetime of that API's references to your object?
eg.
typedef std::shared_ptr<MyClass> MyPtr;
class APIWrapper
{
// could have multiple references to the same thing?
// use multiset instead!
// are references local to transient messages processed in FIFO order?
// use a queue! ... etc. etc.
std::set<MyPtr, SuitableComparator> references_;
public:
void send(MyPtr const &ref)
{
references_.insert(ref);
system_api_send_obj(ref.get());
}
MyPtr receive(void *api)
{
MyPtr ref( static_cast<MyClass *>(api)->shared_from_this() );
references_.erase(ref);
return ref;
}
};
Obviously (hopefully) you know the actual ownership semantics of your API, so the above is just a broad guess.
How about using the following global functions that use pointers-to-smart-pointers.
template<typename T> void *shared_lock(std::shared_ptr<T> &sp)
{
return new std::shared_ptr<T>(sp);
}
template<typename T> std::shared_ptr<T> shared_unlock(void *vp)
{
std::shared_ptr<T> *psp = static_cast<std::shared_ptr<T,D>*>(sp);
std::shared_ptr<T> res(*psp);
delete psp;
return res;
}
You have an extra new/delete but the original type is unmodified. And additionally, concurrency is not an issue, because each call to shared_lock will return a different shared_ptr.
To avoid the new/delete calls you could use an object pool, but I think that it is not worth the effort.
UPDATE:
If you are not going to use multiple threads in this matter, what about the following?
template<typename T> struct SharedLocker
{
std::vector< std::shared_ptr<T> > m_ptrs;
unsigned lock(std::shared_ptr<T> &sp)
{
for (unsigned i = 0; i < m_ptrs.size(); ++i)
{
if (!m_ptrs[i])
{
m_ptrs[i] = sp;
return i;
}
}
m_ptrs.push_back(sp);
return m_ptrs.size() - 1;
}
std::shared_ptr<T> unlock(unsigned i)
{
return std::move(m_ptrs[i]);
}
};
I've changed the void* into an unsigned, but that shouldn't be a problem. You could also use intptr_t, if needed.
Assuming that most of the time there will be only a handful of objects in the vector, maybe even no more than 1, the memory allocations will only happen once, on first insertion. And the rest of the time it will be at zero cost.