Search code examples
c++smart-pointers

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


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.


Solution

  • 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.