为什么Clang和VS2013接受移动大括号初始化的默认参数,但不接受GCC 4.8或4.9?

问题描述:

就像标题所暗示的,我有一个简短的演示程序,可以编译所有这些编译器,但在使用gcc 4.8和gcc 4.9编译后运行核心转储:为什么Clang和VS2013接受移动大括号初始化的默认参数,但不接受GCC 4.8或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 ***

+2

你在gcc上使用了哪些标志? – ThomasMcLeod

+0

为什么在使用这个构造函数时会发生:'Bar(Foo f = {Foo()}):_f(std :: move(f)){} – ThomasMcLeod

+0

它的核心转储,原因相同。 – vmrob

更新

它会出现一个fix for the problem has been checked in


有趣的问题。它肯定似乎是GCC处理= {}初始化默认参数的一个错误,这是一个late addition to the standard。这个问题可以用一个非常简单的类的地方被复制的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; 
} 

Compiled with 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似乎产生了一个额外的对象(虽然它只是调用构造函数和析构函数各一次),当您使用此语法:

#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; 
} 

Output

In constructor: 0x7fffbf873ebf 
In DefArgFunc: 0x7fffbf873ea0 
In destructor: 0x7fffbf873ebf 

改变从SimpleClass x = {}默认参数SimpleClass x = SimpleClass{}产生

In constructor: 0x7fffdde483bf 
In DefArgFunc: 0x7fffdde483bf 
In destructor: 0x7fffdde483bf 

如预期。

似乎在发生的事情是创建对象,调用默认构造函数,然后执行与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); 

将参数传递给构造(Bar b(Foo{});),而不是使用默认的说法也解决了这个问题。

+0

是的,这绝对是一个在gcc 4.9(或至少我拥有的快照)中存在的bug。clang没有相同的错误。已经有一个错误报告和其他一些看似相似的报告。但是为了澄清测试用例的问题+1。如果您执行'SimpleClass x = SimpleClass {}',问题就会消失。 – 2014-01-15 19:39:30

+0

已经有报道! http://gcc.gnu.org/bugzilla/show_bug。cgi?id = 59713 – vmrob

+0

是的,对不起,我现在看到关于这个问题的完整评论链。我以前没有读过所有隐藏的评论。哦,我发现它是一个有趣的练习:) – jerry