Unordered_map класса после идиомы pImpl с использованием unique_ptr

Вот упрощенный код: 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, и это работает, но у меня вопрос: почему?

Вероятно, вы захотите использовать std::piecewise_construct.

n. m. could be an AI 12.03.2024 18:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
111
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

это компилируется, если вы измените определение класса, чтобы иметь явно определенный конструктор перемещения.

работа с 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 12.03.2024 20:30

@LoPiTaL вы правы, ответ был обновлен, чтобы отразить причину.

Ahmed AEK 12.03.2024 20:40

Проблема решена, если объявление и реализация класса находятся в одном файле: godbolt.org/z/4TEcYfa4h Но по-прежнему возникает ошибка компиляции, когда объявление находится в заголовочном файле: error: invalid application of 'sizeof' to incomplete type 'Foo::Impl' вот пример: godbolt.org/ z/z4rsbPMfx

omarekik 12.03.2024 22:40

@omarekik операции перемещения должны быть определены в cpp, только их объявление находится в заголовке, то есть: поместите = default в cpp, а не в заголовок, как показано в ответе.

Ahmed AEK 12.03.2024 22:43

Поскольку вы явно определили деструктор для 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;

(хотя не следует беспокоиться о файлере заголовка и модуле реализации.)

Другие вопросы по теме