c++c++17unique-ptrstd-pairpimpl-idiom

unordered_map of class following pImpl idiom using unique_ptr


Here is a simplified code: https://godbolt.org/z/EnE76xMrP
The pImpl will contain a mutex member which make the pImpl neither copyable nor movable. But the Foo class has unique_ptr of pImpl as member which make Foo movable but not copyable.

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

For mapping keys to Foo values, I used emplace. But it looks even constructing a pair<int, Foo> is not feasible. Would you please clarify ? Because the compiler message is somewhat cumbersome: no matching function for call to 'std::pair<int, Foo>::pair(int, Foo)'
I tried to use shared_ptr instead of unique_ptr, and it works, but my question is why?


Solution

  • this compiles if you modify the class definition to have an explicitly defined move constructor.

    working with Pimpl requires all "exposed" constructors and destructors to be declared in the headers, and implemented in the 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;
    

    you also need a move assignment operator Foo& operator=(Foo&&) if you ever intend to move assign it.

    also note that you should use std::pair<int, Foo> not std::pair<int, Foo&&> as this rvalue Reference would be dangling, hence the SIGSEGV


    Edit: the reason you need to explicitly define the move constructor is because unique_ptr move constructor will try to invoke Impl destructor which is only defined inside the cpp file.