Вот упрощенный код: https://godbolt.org/z/EnE76xMrP
pImpl будет содержать член мьютекса, который делает pImpl не подлежащим копированию и перемещению. Но класс Foo имеет unique_ptr из pImpl в качестве члена, что делает Foo перемещаемым, но не копируемым.
class Foo final
{
public:
Foo();
~Foo();
void DoSomething(const int thread_num);
private:
int data{};
struct Impl;
std::unique_ptr<Impl> m_impl;
};
struct Foo::Impl final
{
mutable std::mutex m_mutex;
};
Foo::Foo()
: m_impl(std::make_unique<Impl>())
{}
Foo::~Foo() {
std::cout << "Foo dtor \n";
}
Для сопоставления ключей со значениями Foo я использовал emplace. Но похоже, что даже построить пару <int, Foo> невозможно. Не могли бы вы уточнить? Потому что сообщение компилятора несколько громоздкое:
no matching function for call to 'std::pair<int, Foo>::pair(int, Foo)'
Я пытался использоватьshared_ptr вместо unique_ptr, и это работает, но у меня вопрос: почему?





это компилируется, если вы измените определение класса, чтобы иметь явно определенный конструктор перемещения.
работа с Pimpl требует, чтобы все «открытые» конструкторы и деструкторы были объявлены в заголовках и реализованы в cpp.
class Foo final
{
public:
Foo();
~Foo();
Foo(Foo&&) noexcept;
Foo& operator=(Foo&&) noexcept;
void DoSomething(const int thread_num);
private:
int data{};
struct Impl;
std::unique_ptr<Impl> m_impl;
};
// in the cpp
Foo::Foo(): m_impl(std::make_unique<Impl>()) {};
Foo::~Foo() = default;
Foo::Foo(Foo&&) noexcept = default;
Foo& Foo::operator=(Foo&&) noexcept = default;
вам также понадобится оператор присваивания перемещения Foo& operator=(Foo&&), если вы когда-нибудь собираетесь его переместить.
также обратите внимание, что вам следует использовать std::pair<int, Foo>, а не std::pair<int, Foo&&>, поскольку эта ссылка на значение rvalue будет висеть, следовательно, SIGSEGV
Обновлено: причина, по которой вам необходимо явно определить конструктор перемещения, заключается в том, что конструктор перемещения попытается вызвать деструктор unique_ptr, который определен только внутри файла cpp.
Это связано с тем, что конструкторы/назначения и деструкторы копирования/перемещения делегируют эти операции всем членам (включая элемент unique_ptr), который, в свою очередь, делегирует их своему управляемому экземпляру T. Для этого необходимо иметь полное определение (чтобы компилятор знал, какие члены T реализуют и тому подобное). Поскольку в шапке это невозможно, просто удаляются члены по умолчанию. Вы должны явно реализовать их (даже если просто использовать их по умолчанию) в файле cpp, где доступно полное определение.
@LoPiTaL вы правы, ответ был обновлен, чтобы отразить причину.
Проблема решена, если объявление и реализация класса находятся в одном файле: godbolt.org/z/4TEcYfa4h Но по-прежнему возникает ошибка компиляции, когда объявление находится в заголовочном файле: error: invalid application of 'sizeof' to incomplete type 'Foo::Impl' вот пример: godbolt.org/ z/z4rsbPMfx
@omarekik операции перемещения должны быть определены в cpp, только их объявление находится в заголовке, то есть: поместите = default в cpp, а не в заголовок, как показано в ответе.
Поскольку вы явно определили деструктор для Foo, компилятор предполагает, что в вашем классе есть что-то особенное, и не генерирует конструктор перемещения по умолчанию. См. например Конструктор перемещения
Неявно объявленный конструктор перемещения
Если для типа класса не предусмотрены определяемые пользователем конструкторы перемещения и все следующие условия верны:
- нет объявленных пользователем конструкторов копирования;
- нет объявленных пользователем операторов присваивания копий;
- нет объявленных пользователем операторов присваивания перемещения;
- нет объявленного пользователем деструктора.
Затем компилятор объявит конструктор перемещения как неявный встроенный публичный член своего класса с подписью
T::T(T&&).
Если вы удалите определение конструктора, ваша программа компилируется и работает нормально.
class Foo final
{
public:
Foo();
//~Foo();
void DoSomething(const int thread_num);
private:
int data{};
struct Impl;
std::unique_ptr<Impl> m_impl;
};
Как было предложено в другом ответе, вы также можете определить операции перемещения как:
Foo(Foo&&) = default;
Foo& operator=(Foo&&) = default;
(хотя не следует беспокоиться о файлере заголовка и модуле реализации.)
Вероятно, вы захотите использовать std::piecewise_construct.