Is it possible to reuse a smart pointer's memory for another object?

Viewed 236

Let's say I have two unrelated types that are exactly the same size.

struct Foo { ... };
struct Bar { ... };

And now, let's say that I have a tree of Foo that I would like to convert to a tree of Bar.

template <typename Leaf>
struct Tree
{
    Leaf data;
    std::unique_ptr<Tree> lhs;
    std::unique_ptr<Tree> rhs;
};

Tree<Foo> footree = ...;
Tree<Bar> bartree = convert(std::move(footree)); // <--

How could I perform this conversion while reusing the heap-allocated memory of footree? In other words, how can I (safely) convert a Tree<Foo> to a Tree<Bar> with no extra memory allocations?

This is a simplified example of what I'm actually trying to do. In reality Foo and Bar are recursive variants with differing numbers of alternative types.

1 Answers

For raw pointers it isn't difficult. One simply needs to trigger destructor of Foo manually and apply constructor manually via placement new. The enable_if is SFINAE that ensures that sizes and alignment match.

#include <iostream>
#include <vector>

template<typename U, typename V, typename... Args,
         std::enable_if_t<sizeof(U) == sizeof(V) && alignof(U) == alignof(V), int> = 0>
U* repurpose_raw_ptr(V* ptr, Args&&... args)
{
    ptr->~V();
    return new ((void*)ptr) U(std::forward<Args>(args)...);
}

int main()
{
    std::vector<int>* X = new std::vector<int>({1,2,3,4,5});
    std::vector<double>* Y =
        repurpose_raw_ptr<std::vector<double>>(X,
            std::initializer_list<double>{1.,2.,3.,4.,5.});

    std::cout << Y->at(0) << " " << Y->at(4);

    delete Y;

    return 0;
}

You can apply the function to "convert" from std::unique_ptr<Foo> to std::unique_ptr<Bar> by using the release() method of unique pointer where it releases ownership of the object. Something like:

std::unique_ptr<Bar> X = ...;
std::unique_ptr<Foo> Y = std::unique_ptr<Foo>(repurpose_raw_ptr<Bar>(X.release(),...));

Here it assumes delete Foo and delete Bar is same as destruction of Foo/Bar + dealloc of a raw pointer which might not always be the case I believe.

In general, I do not recommend doing something like this as it is hard to ensure that structures have same size/alignment in a cross-platform environment.

Edit: Formerly, missed that you wanted to convert Foo to Bar in some way. You'll have to temporarily copy/move instance Foo elsewhere and then trigger trigger the memory repurposing.

Related