std::unique_ptr 在虚拟析构函数上重置 SIGABRT
std::unique_ptr reset SIGABRT on virtual destructor
我花了一些时间试图追踪这个问题,但我在这里有一个小例子来展示我看到的错误。如果我用 reset 省略该行,它就可以正常工作。
#include <memory>
#include <unordered_map>
#include <iostream>
class Base {
public:
virtual ~Base() = 0;
};
Base::~Base(){}
class D1 : public Base {
public:
~D1(){}
};
class D2 : public Base {
public:
~D2(){}
};
struct Foo {
using MyMap = std::unordered_map<std::size_t, std::unique_ptr<Base>>;
MyMap _test;
};
int main(){
Foo f;
/** Works fine **/
f._test[12] = std::make_unique<D2>();
f._test[1] = std::make_unique<D1>();
D2 newD2;
f._test[12].reset(&newD2);
/** Execution reaches here **/
std::cout<<"Foo!"<<std::endl;
/** Sigabrt on cleanup **/
return 0;
}
程序编译正常
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-pc-linux-gnu/6.3.1/lto-wrapper
Target: i686-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --disable-multilib --disable-werror --enable-checking=release
Thread model: posix
gcc version 6.3.1 20170306 (GCC)
没有警告或错误。但是当程序运行和退出时,它似乎在清理时发出信号。 Valgrind 有以下说法
==24694== Memcheck, a memory error detector
==24694== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==24694== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==24694== Command: ./test
==24694==
Foo!
pure virtual method called
terminate called without an active exception
==24694==
==24694== Process terminating with default action of signal 6 (SIGABRT): dumping core
==24694== at 0x427E502: raise (in /usr/lib/libc-2.25.so)
==24694== by 0x427FCD6: abort (in /usr/lib/libc-2.25.so)
==24694== by 0x40CC6CE: __gnu_cxx::__verbose_terminate_handler() (vterminate.cc:95)
==24694== by 0x40CA063: __cxxabiv1::__terminate(void (*)()) (eh_terminate.cc:47)
==24694== by 0x40CA0DC: std::terminate() (eh_terminate.cc:57)
==24694== by 0x40CAED3: __cxa_pure_virtual (pure.cc:50)
==24694== by 0x8049A29: std::default_delete<Base>::operator()(Base*) const (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A946: std::unique_ptr<Base, std::default_delete<Base> >::~unique_ptr() (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A74C: std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >::~pair() (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A764: void __gnu_cxx::new_allocator<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >::destroy<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >(std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >*) (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A3D6: void std::allocator_traits<std::allocator<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > > >::destroy<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >(std::allocator<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >&, std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >*) (in /home/aryan/Desktop/Gists/test)
==24694== by 0x8049F65: std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >, false> > >::_M_deallocate_node(std::__detail::_Hash_node<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >, false>*) (in /home/aryan/Desktop/Gists/test)
==24694==
==24694== HEAP SUMMARY:
==24694== in use at exit: 32 bytes in 2 blocks
==24694== total heap usage: 8 allocs, 6 frees, 20,028 bytes allocated
==24694==
==24694== LEAK SUMMARY:
==24694== definitely lost: 0 bytes in 0 blocks
==24694== indirectly lost: 0 bytes in 0 blocks
==24694== possibly lost: 0 bytes in 0 blocks
==24694== still reachable: 32 bytes in 2 blocks
==24694== suppressed: 0 bytes in 0 blocks
==24694== Rerun with --leak-check=full to see details of leaked memory
==24694==
==24694== For counts of detected and suppressed errors, rerun with: -v
==24694== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Aborted (core dumped)
至于问题,我是漏掉了一些非常明显的东西还是这是一个错误?我现在手头没有其他编译器可以测试..
D2 newD2;
f._test[12].reset(&newD2);
您为 unique_ptr
提供了一个指向具有自动生命周期的变量的指针。那是不行的。
当 unique_ptr
超出范围时,它会尝试执行 delete ptr;
但是 ptr
从未分配给 new
所以你的 delete
失败了,你获得一个不错的 SIGABRT。 newD2
会被自己销毁,因为它有自动生命周期。
无论有没有虚拟,这都是未定义的行为,虚拟可能只是 SIGABRT 的触发器。
不要将 smart_pointers 用于不是由 new 运算符分配或由 std::make_shared[ 创建的对象=20=] 或 std::make_unique.
在您的示例中,newD2 具有明确的范围。当它离开它的范围时(在 return 之后)它的析构函数被自动调用。
在 C++ 中,RAII 保证在对象超出范围时调用对象的析构函数。
我花了一些时间试图追踪这个问题,但我在这里有一个小例子来展示我看到的错误。如果我用 reset 省略该行,它就可以正常工作。
#include <memory>
#include <unordered_map>
#include <iostream>
class Base {
public:
virtual ~Base() = 0;
};
Base::~Base(){}
class D1 : public Base {
public:
~D1(){}
};
class D2 : public Base {
public:
~D2(){}
};
struct Foo {
using MyMap = std::unordered_map<std::size_t, std::unique_ptr<Base>>;
MyMap _test;
};
int main(){
Foo f;
/** Works fine **/
f._test[12] = std::make_unique<D2>();
f._test[1] = std::make_unique<D1>();
D2 newD2;
f._test[12].reset(&newD2);
/** Execution reaches here **/
std::cout<<"Foo!"<<std::endl;
/** Sigabrt on cleanup **/
return 0;
}
程序编译正常
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-pc-linux-gnu/6.3.1/lto-wrapper
Target: i686-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --disable-multilib --disable-werror --enable-checking=release
Thread model: posix
gcc version 6.3.1 20170306 (GCC)
没有警告或错误。但是当程序运行和退出时,它似乎在清理时发出信号。 Valgrind 有以下说法
==24694== Memcheck, a memory error detector
==24694== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==24694== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==24694== Command: ./test
==24694==
Foo!
pure virtual method called
terminate called without an active exception
==24694==
==24694== Process terminating with default action of signal 6 (SIGABRT): dumping core
==24694== at 0x427E502: raise (in /usr/lib/libc-2.25.so)
==24694== by 0x427FCD6: abort (in /usr/lib/libc-2.25.so)
==24694== by 0x40CC6CE: __gnu_cxx::__verbose_terminate_handler() (vterminate.cc:95)
==24694== by 0x40CA063: __cxxabiv1::__terminate(void (*)()) (eh_terminate.cc:47)
==24694== by 0x40CA0DC: std::terminate() (eh_terminate.cc:57)
==24694== by 0x40CAED3: __cxa_pure_virtual (pure.cc:50)
==24694== by 0x8049A29: std::default_delete<Base>::operator()(Base*) const (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A946: std::unique_ptr<Base, std::default_delete<Base> >::~unique_ptr() (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A74C: std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >::~pair() (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A764: void __gnu_cxx::new_allocator<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >::destroy<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >(std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >*) (in /home/aryan/Desktop/Gists/test)
==24694== by 0x804A3D6: void std::allocator_traits<std::allocator<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > > >::destroy<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >(std::allocator<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > > >&, std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >*) (in /home/aryan/Desktop/Gists/test)
==24694== by 0x8049F65: std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >, false> > >::_M_deallocate_node(std::__detail::_Hash_node<std::pair<unsigned int const, std::unique_ptr<Base, std::default_delete<Base> > >, false>*) (in /home/aryan/Desktop/Gists/test)
==24694==
==24694== HEAP SUMMARY:
==24694== in use at exit: 32 bytes in 2 blocks
==24694== total heap usage: 8 allocs, 6 frees, 20,028 bytes allocated
==24694==
==24694== LEAK SUMMARY:
==24694== definitely lost: 0 bytes in 0 blocks
==24694== indirectly lost: 0 bytes in 0 blocks
==24694== possibly lost: 0 bytes in 0 blocks
==24694== still reachable: 32 bytes in 2 blocks
==24694== suppressed: 0 bytes in 0 blocks
==24694== Rerun with --leak-check=full to see details of leaked memory
==24694==
==24694== For counts of detected and suppressed errors, rerun with: -v
==24694== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Aborted (core dumped)
至于问题,我是漏掉了一些非常明显的东西还是这是一个错误?我现在手头没有其他编译器可以测试..
D2 newD2;
f._test[12].reset(&newD2);
您为 unique_ptr
提供了一个指向具有自动生命周期的变量的指针。那是不行的。
当 unique_ptr
超出范围时,它会尝试执行 delete ptr;
但是 ptr
从未分配给 new
所以你的 delete
失败了,你获得一个不错的 SIGABRT。 newD2
会被自己销毁,因为它有自动生命周期。
无论有没有虚拟,这都是未定义的行为,虚拟可能只是 SIGABRT 的触发器。
不要将 smart_pointers 用于不是由 new 运算符分配或由 std::make_shared[ 创建的对象=20=] 或 std::make_unique.
在您的示例中,newD2 具有明确的范围。当它离开它的范围时(在 return 之后)它的析构函数被自动调用。
在 C++ 中,RAII 保证在对象超出范围时调用对象的析构函数。