我是否误解了这个默认参数 shared_ptr 的范围?

Have I misunderstood the scope of this default argument shared_ptr?

看看这个:

#include <iostream>
#include <memory>

using Foo = int;
using FooPtr = std::shared_ptr<Foo>;

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?)

我预计 shared_ptr 删除器会在 bar() returns 时被调用,而在我使用 GCC 4.8.5 的 64 位 CentOS 7 系统上,确实如此。

但是,在我的 32 位 CentOS 6 系统上,在 devtoolset-2 下使用 GCC 4.8.2(我 认为 gcc-linaro-arm-linux-gnueabihf-4.8-2013.10_linux 下,我的 Raspberry Pi 工具链),它不会。

查看代码,考虑到 C++11 在 4.8 中的实验性质,我觉得这像是一个编译器错误。但我也可能在某个地方陷入了 UB 陷阱(或者只是普遍误解了这些东西应该如何工作)。

谁的错?我该如何解决?


工作于

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)

析构函数应在 bar return 时或调​​用 bar 的完整表达式结束时调用。

如果我们查看 [[=​​25=]]/4(C++17 草案)我们有

When a function is called, each parameter (11.3.5) shall be initialized (11.6, 15.8, 15.1) with its corresponding argument.[...]It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression.[...]

所以p应该在函数开始时初始化为null FooPtr,移动赋值给MakeFoo的return,最后销毁(在在 bar 末尾或 bar return 秒后 main.

转调用删除器)

正如 Nathan 所展示的,我对指针生命周期的假设是 standard-correct。

删除器未被调用似乎是 GCC 或 libstdc++ 错误,可能 bug 60367 考虑到链接的评论解决了它,症状看起来很相似并且在 GCC 4.8.5 之前已修复.

= FooPtr{} 替换 = {} 似乎是一个可行的解决方法。


注意哦,还有a regression in 7.2 and some older "8.0" trunk builds that may cause bad behaviour in similar circumstances (thanks Arne Vogel!).