为什么Clang和VS2013接受移动的大括号默认参数,而不是GCC 4.8或4.9?
就像标题所示,我有一个简短的演示程序,可以编译所有这些编译器,但在使用gcc 4.8和gcc 4.9编译后运行核心转储:
任何想法,为什么?
#include <unordered_map> struct Foo : std::unordered_map<int,int> { using std::unordered_map<int, int>::unordered_map; // ~Foo() = default; // adding this allows it to work }; struct Bar { Bar(Foo f = {}) : _f(std::move(f)) {} // using any of the following constructors fixes the problem: // Bar(Foo f = Foo()) : _f(std::move(f)) {} // Bar(Foo f = {}) : _f(f) {} Foo _f; }; int main() { Bar b; // the following code works as expected // Foo f1 = {}; // Foo f2 = std::move(f1); }
我的编译设置:
g++ --std=c++11 main.cpp
这是GDB的回溯:
#0 0x00007fff95d50866 in __pthread_kill () #1 0x00007fff90ba435c in pthread_kill () #2 0x00007fff8e7d1bba in abort () #3 0x00007fff9682e093 in free () #4 0x0000000100002108 in __gnu_cxx::new_allocator<std::__detail::_Hash_node_base*>::deallocate () #5 0x0000000100001e7d in std::allocator_traits<std::allocator<std::__detail::_Hash_node_base*> >::deallocate () #6 0x0000000100001adc in std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<int const, int>, false> > >::_M_deallocate_buckets () #7 0x000000010000182e in std::_Hashtable<int, std::pair<int const, int>, std::allocator<std::pair<int const, int> >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_deallocate_buckets () #8 0x000000010000155a in std::_Hashtable<int, std::pair<int const, int>, std::allocator<std::pair<int const, int> >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::~_Hashtable () #9 0x000000010000135c in std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, int> > >::~unordered_map () #10 0x00000001000013de in Foo::~Foo () #11 0x0000000100001482 in Bar::~Bar () #12 0x0000000100001294 in main ()
*** error for object 0x1003038a0: pointer being freed was not allocated ***
更新
这似乎是一个问题的解决办法已经签入 。
有趣的问题。 这肯定是GCC处理初始化默认参数的一个错误,这是标准的一个延迟 。 这个问题可以用一个非常简单的类来代替std::unordered_map<int,int>
:
#include <utility> struct PtrClass { int *p = nullptr; PtrClass() { p = new int; } PtrClass(PtrClass&& rhs) : p(rhs.p) { rhs.p = nullptr; } ~PtrClass() { delete p; } }; void DefArgFunc(PtrClass x = {}) { PtrClass x2{std::move(x)}; } int main() { DefArgFunc(); return 0; }
用g ++(Ubuntu 4.8.1-2ubuntu1〜12.04)编译4.8.1 ,显示同样的问题:
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000001aa9010 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7fc2cd196b96] ./a.out[0x400721] ./a.out[0x4006ac] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7fc2cd13976d] ./a.out[0x400559] ======= Memory map: ======== bash: line 7: 2916 Aborted (core dumped) ./a.out
深入挖掘一下,GCC似乎创build了一个额外的对象(虽然每次只调用构造函数和析构函数),但是当你使用这个语法时:
#include <utility> #include <iostream> struct SimpleClass { SimpleClass() { std::cout << "In constructor: " << this << std::endl; } ~SimpleClass() { std::cout << "In destructor: " << this << std::endl; } }; void DefArgFunc(SimpleClass x = {}) { std::cout << "In DefArgFunc: " << &x << std::endl; } int main() { DefArgFunc(); return 0; }
输出 :
In constructor: 0x7fffbf873ebf In DefArgFunc: 0x7fffbf873ea0 In destructor: 0x7fffbf873ebf
将默认参数从SimpleClass x = {}
更改为SimpleClass x = SimpleClass{}
In constructor: 0x7fffdde483bf In DefArgFunc: 0x7fffdde483bf In destructor: 0x7fffdde483bf
如预期。
似乎正在发生的事情是,创build一个对象,调用默认构造函数,然后执行类似于memcpy
的操作。 这个“鬼物体”是传递给移动构造函数并进行修改的。 但是,析构函数是在原始的,未修改的对象上调用的,该对象现在与移动构造的对象共享一些指针。 两个最终都试图释放它,造成这个问题。
根据上面的解释,您注意到的四个变化解决了问题:
// 1 // adding the destructor inhibits the compiler generated move constructor for Foo, // so the copy constructor is called instead and the moved-to object gets a new // pointer that it doesn't share with the "ghost object", hence no double-free ~Foo() = default; // 2 // No `= {}` default argument, GCC bug isn't triggered, no "ghost object" Bar(Foo f = Foo()) : _f(std::move(f)) {} // 3 // The copy constructor is called instead of the move constructor Bar(Foo f = {}) : _f(f) {} // 4 // No `= {}` default argument, GCC bug isn't triggered, no "ghost object" Foo f1 = {}; Foo f2 = std::move(f1);
将parameter passing给构造函数( Bar b(Foo{});
)而不是使用默认参数也解决了这个问题。