Mira esto:
#include #include using Foo = int; using FooPtr = std::shared_ptr; FooPtr makeFoo() { FooPtr f{ new Foo(), [](Foo* ptr) { delete ptr; std::cerr << "!\n"; } }; return f; } void bar(FooPtr p = {}) { p = makeFoo(); } int main() { bar(); } // Expected output: '!' // Failure case: no output (deleter not invoked?)
Esperaba que se shared_ptr
el shared_ptr
shared_ptr cuando vuelva bar()
, y en mi sistema CentOS 7 de 64 bits que usa GCC 4.8.5, lo hace.
Sin embargo, en mi sistema CentOS 6 de 32 bits que utiliza GCC 4.8.2 en devtoolset-2 (también creo que en gcc-linaro-arm-linux-gnueabihf-4.8-2013.10_linux
, mi cadena de herramientas Raspberry Pi), no es así.
Mirando el código, y dada la naturaleza experimental de C ++ 11 en 4.8, esto me huele a error de comstackción. Pero también podría estar cayendo en una trampa de UB en algún lugar (o simplemente malinterpretando cómo debería funcionar esto).
¿Quién tiene la culpa? ¿Y cómo debo arreglarlo?
Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper Target: x86_64-redhat-linux Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux Thread model: posix gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/opt/rh/devtoolset-2/root/usr/libexec/gcc/i686-redhat-linux/4.8.2/lto-wrapper Target: i686-redhat-linux Configured with: ../configure --prefix=/opt/rh/devtoolset-2/root/usr --mandir=/opt/rh/devtoolset-2/root/usr/share/man --infodir=/opt/rh/devtoolset-2/root/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --enable-languages=c,c++,fortran,lto --enable-plugin --with-linker-hash-style=gnu --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.2-20140120/obj-i686-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.2-20140120/obj-i686-redhat-linux/cloog-install --with-mpc=/builddir/build/BUILD/gcc-4.8.2-20140120/obj-i686-redhat-linux/mpc-install --with-tune=generic --with-arch=i686 --build=i686-redhat-linux Thread model: posix gcc version 4.8.2 20140120 (Red Hat 4.8.2-15) (GCC)
Se debe llamar al destructor cuando regresa la bar
o al final de la expresión completa en la que se llama la bar
.
Si observamos [expr.call] / 4 (borrador de C ++ 17) tenemos
Cuando se llama a una función, cada parámetro (11.3.5) se inicializará (11.6, 15.8, 15.1) con su argumento correspondiente. […] Se define por implementación si la vida útil de un parámetro finaliza cuando la función en la que Se define como retornos o al final de la expresión completa adjunta. […]
Por lo tanto, se debe inicializar p
a un FooPtr
nulo al comienzo de la función, mover el retorno asignado de MakeFoo
y finalmente destruirlo (a su vez llamar al eliminador) al final de la bar
o después de que la bar
regrese en main
.
Como ha demostrado Nathan, mis suposiciones sobre la vida útil del puntero eran correctas en cuanto a los estándares.
El hecho de que el deleter no se haya invocado parece ser un error de GCC o libstdc ++, posiblemente el error 60367 dado que el comentario vinculado lo resuelve, los síntomas parecen similares y se corrigió antes de GCC 4.8.5.
Reemplazar = {}
con = FooPtr{}
parece ser una solución viable.
Tenga en cuenta que también hay una regresión en 7.2 y algunas versiones anteriores de “8.0” que pueden causar un mal comportamiento en circunstancias similares (gracias Arne Vogel !).