Have I misunderstood the scope of this default arg

2019-06-17 05:10发布

Take a look at this:

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

I expected the shared_ptr deleter to be called when bar() returns, and on my 64-bit CentOS 7 system using GCC 4.8.5, it does.

However, on my 32-bit CentOS 6 system using GCC 4.8.2 under devtoolset-2 (also I think under gcc-linaro-arm-linux-gnueabihf-4.8-2013.10_linux, my Raspberry Pi toolchain), it doesn't.

Looking at the code, and given C++11's experimental nature in 4.8, this smells like a compiler bug to me. But I could also be falling into a UB trap somewhere (or just generally misunderstanding how this stuff ought to work).

Who's at fault? And how should I fix it?


Works on

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)

Fails on

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)

2条回答
放荡不羁爱自由
2楼-- · 2019-06-17 05:28

The destructor should be called either when bar returns or and the end of the full expression in which bar is called.

If we look at [expr.call]/4 (C++17 draft) we have

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.[...]

So p should be initialized to a null FooPtr at the start of the function, move assigned the return of MakeFoo, and then finally destroyed (in turn calling the deleter) at the end of bar or after bar returns in main.

查看更多
闹够了就滚
3楼-- · 2019-06-17 05:36

As Nathan has shown, my assumptions about the pointer's lifetime were standard-correct.

That the deleter isn't being invoked does appear to be a GCC or libstdc++ bug, possibly bug 60367 given that the linked comment solves it, the symptoms seem similarish and it was fixed before GCC 4.8.5.

Replacing = {} with = FooPtr{} appears to be a viable workaround.


Note well, there is also a regression in 7.2 and some older "8.0" trunk builds that may cause bad behaviour in similar circumstances (thanks Arne Vogel!).

查看更多
登录 后发表回答