Evitar movimientos adicionales en make_unique / make_shared / emplace / etc para estructuras que usan inicialización agregada
std::make_unique()
(y funciones similares) tienen un pocoproblema:
#include <cstdio>
#include <memory>
using namespace std;
struct S
{
S() { printf("ctor\n"); }
~S() { printf("dtor\n"); }
S(S const&) { printf("cctor\n"); }
S(S&&) { printf("mctor\n"); }
};
S foo() { return S(); }
int main()
{
{
printf("--------------- case 1 ---------------\n");
unique_ptr<S> s1 = make_unique<S>( foo() );
}
{
printf("--------------- case 2 ---------------\n");
unique_ptr<S> s2 { new S( foo() ) };
}
}
Salida:
--------------- case 1 ---------------
ctor
mctor
dtor
dtor
--------------- case 2 ---------------
ctor
dtor
Como puede ver, tenemos un movimiento adicional que se puede evitar.Mismo problema existe conemplace()
en opcional / variante / etc: si otra función devuelve el objeto, debe moverlo.
Esto puede serdirigido con un truco:
#include <cstdio>
#include <optional>
using namespace std;
struct S
{
S() { printf("ctor\n"); }
~S() { printf("dtor\n"); }
S(S const&) { printf("cctor\n"); }
S(S&&) { printf("mctor\n"); }
template<class F, enable_if_t<is_same_v<invoke_result_t<F>, S>>...>
S(F&& f) : S(forward<F>(f)()) {}
};
S foo() { return S(); }
int main()
{
optional<S> s;
s.emplace( []{ return foo(); } );
}
Esto evita movimientos innecesarios (enable_if oculta el constructor a menos quef()
devuelve una instancia de S). Efectivamente terminas construyendo tus valores dentro destd::variant
/std::optional
/ etc a través de una llamada a su función de construcción.
Esta solución tiene un pequeño problema: agregar un constructor rompe la inicialización agregada. Verejemplo. Es decir. si la estructura dada no tenía constructor y agrega uno, ya no puede inicializarlo así:
struct D
{
float m;
S s;
// adding new constructor here will break existing bar() functions
};
D bar() { /*...lots of code with multiple return statements...*/ return {2.0, foo()}; }
Pregunta: ¿Hay alguna forma de evitar este problema? Algo que no introduce nuevos constructores ...
Me gustaría poder poner eficientemente mis estructuras en opcional / variant / shared_ptr-block / etc sin romper el código (más bien no trivial) que las crea.