Ответ 1
Update
Похоже, что исправление проблемы было проверено в.
Интересный вопрос. Кажется, что это ошибка в том, как 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, кажется, создает дополнительный объект (хотя он вызывает только конструктор и деструктор один раз), когда вы используете этот синтаксис:
#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
как ожидалось.
Кажется, что происходит создание объекта, вызывается конструктор по умолчанию, а затем выполняется нечто похожее на 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{});
) вместо использования аргумента по умолчанию также решает проблему.