为什么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{}); )而不是使用默认参数也解决了这个问题。