The Forest And The Trees
The Forest And The Trees

Reputation: 1856

Using shared pointers with memory allocated in another function

I have some legacy-era code at work that takes in a double-pointer and allocates memory to it. A shortened example of it would look something like this:

struct LegacyObj
{
    int a;
    double b;
};

void LegacyAllocator(LegacyObj** ppObj)
{
    *ppObj = (LegacyObj*)malloc(sizeof(LegacyObj));
}

void LegacyDeleter(LegacyObj** ppObj)
{
    free(*ppObj);
}

The actual LegacyAllocator function is ~100 lines and mixes reading from files with creating a linked list of LegacyObj pointers, and isn't something I'd be able to get away with rewriting right now. I would like, however, to make the use of this function a bit safer, avoiding any memory leaks that may occur from exceptions &tc. The first solution I came up with was to wrap it up in a class and handle calling the legacy functions in the ctor/dtor.

class RAIIWrapper
{
public:
    RAIIWrapper()
        :obj{nullptr}
    {
        ::LegacyAllocator(&obj);
    }
    RAIIWrapper(RAIIWrapper&& that)
        : obj{ that.obj}
    {
        that.obj = nullptr;
    }
    RAIIWrapper& operator=(RAIIWrapper&& that)
    {
        RAIIWrapper copy{std::move(that)};
        std::swap(obj, copy.obj);
        return *this;
    }
    ~RAIIWrapper ()
    {
        ::LegacyDeleter(&obj);
    }

private:
    LegacyObj* obj;
};

But I'm curious - is there a way to do this using std::shared_ptr or std::unique_ptr? I've not been able to come up with a solution without having to keep the original pointer passed to LegacyAllocator around.

Upvotes: 6

Views: 384

Answers (3)

Ralph Tandetzky
Ralph Tandetzky

Reputation: 23600

You can create a factory function for unique_ptrs like this:

typedef void(* LegacyDeleterType)(LegacyObj*);
typedef std::unique_ptr<LegacyObj,LegacyDeleterType> UniqueLegacyPtr;

UniqueLegacyPtr makeUniqueLegacyObj()
{
    LegacyObj * p = nullptr;
    LegacyAllocator( &p );
    return UniqueLegacyPtr( p, [](LegacyObj*p){ LegacyDeleter(&p); } );
}

You can now use that to create unique_ptrs and you can also assign to shared_ptrs which capture the custom deleter automatically at construction:

int main()
{
    auto unique = makeUniqueLegacyObj();
    std::shared_ptr<LegacyObj> shared = makeUniqueLegacyObj();
}

Upvotes: 0

vitaut
vitaut

Reputation: 55524

Yes, you can use a custom deleter with std::unique_ptr or std::shared_ptr, for example:

struct Deleter {
  void operator()(LegacyObj *p) const {
    LegacyDeleter(&p);
  }
};

std::unique_ptr<LegacyObj, Deleter> MakeLegacyObj() {
  LegacyObj *p = 0;
  LegacyAllocator(&p);
  return std::unique_ptr<LegacyObj, Deleter>(p);
}

std::unique_ptr<LegacyObj, Deleter> p = MakeLegacyObj();

And, as correctly pointed out by @Dave, this works with shared_ptr too:

std::shared_ptr<LegacyObj> p = MakeLegacyObj();

Upvotes: 3

John Dibling
John Dibling

Reputation: 101446

You can use unique_ptr to delete the memory, but you'll have to provide a custom Deleter class since the memory is allocated using malloc rather than new.

Better yet, change the allocation code to use new instead and just use unique_ptr. If you go down this road you can just have the allocator return a unique_ptr instead of a pointer to the memory.

Assuming you need to provide your own custom deleter, here is one way you might do it:

template <typename T>
class MallocDeleter
{
public:
  void operator() (T* obj) const
  {
    LegacyDeleter (*obj);
  }
};

typedef std::unique_ptr <LegacyObj, MallocDeleter <LegacyObj>> unique_legacy_ptr;

You could also probably provide a make_unique_legacy type function which allocates by calling LegacyAllocator, instead of having to initialize the unique_ptr yourself.

Upvotes: 0

Related Questions