Digamos que tengo dos tipos no relacionados que son exactamente del mismo tamaño.

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

Y ahora, digamos que tengo un árbol de Foo que me gustaría convertir en un árbol de 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)); // <--

¿Cómo podría realizar esta conversión mientras reutilizo la memoria asignada al montón de footree? En otras palabras, ¿cómo puedo convertir (con seguridad) un Tree<Foo> en un Tree<Bar> sin asignaciones de memoria adicionales?

Este es un ejemplo simplificado de lo que realmente estoy tratando de hacer. En realidad, Foo y Bar son variantes recursivas con diferentes números de tipos alternativos.

2
jinscoe123 20 ene. 2021 a las 07:27

1 respuesta

La mejor respuesta

Para los punteros en bruto no es difícil. Uno simplemente necesita activar el destructor de Foo manualmente y aplicar el constructor manualmente a través de la ubicación new. El enable_if es SFINAE que asegura que los tamaños y la alineación coincidan.

#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;
}

Puede aplicar la función para "convertir" de std::unique_ptr<Foo> a std::unique_ptr<Bar> utilizando el método release() de puntero único donde libera la propiedad del objeto. Algo como:

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

Aquí se asume que delete Foo y delete Bar es lo mismo que la destrucción de Foo / Bar + desbloqueo de un puntero en bruto, lo que creo que no siempre es el caso.

En general, no recomiendo hacer algo como esto, ya que es difícil asegurarse de que las estructuras tengan el mismo tamaño / alineación en un entorno multiplataforma.

Editar: anteriormente, se perdió que deseaba convertir Foo a Bar de alguna manera. Tendrá que copiar / mover temporalmente la instancia Foo a otro lugar y luego activar el cambio de propósito de la memoria.

2
Jarod42 20 ene. 2021 a las 09:54