Search code examples
c++c++11vectormove-semanticspimpl-idiom

Storing a class that uses the PIMPL idiom in a std::vector


I am writing an application that needs to store objects of a class that uses the PIMPL idiom in a std::vector. Because the class uses std::unique_ptr to store a pointer to it's implementation and std::unique_ptr is not copyable, the class itself is not copyable. std::vector should still work in this case because the class is still movable.

To avoid creating a copy I tried using emplace_back to construct the elements directly into the vector, but for some reason it still complains that it is trying to call the copy constructor!

I've written a simple example to demonstrate the problem.

test.h:

#pragma once

#include <memory>

// Simple test class implemented using the PIMPL (pointer to implementation) idiom
class Test
{
public:
    Test(const int value);
    ~Test();

    void DoSomething();
private:

    // Forward declare implementation struct
    struct Impl;

    // Pointer to the implementation
    std::unique_ptr<Impl> m_impl;
};

test.cpp

#include "test.h"

#include <iostream>

// Implementation struct definition
struct Test::Impl
{
    Impl(const int value)
        : m_value(value)
    {}

    void DoSomething()
    {
        std::cout << "value = " << m_value << std::endl;
    }

private:
    int m_value;
};

// Construct the class and create an instance of the implementation struct
Test::Test(const int value)
    : m_impl(std::make_unique<Impl>(value))
{}

Test::~Test() = default;

// Forward function calls to the implementation struct
void Test::DoSomething()
{
    m_impl->DoSomething();
}

main.cpp:

#include "test.h"

#include <vector>

int main()
{
    std::vector<Test> v;

    // Even though I'm using "emplace_back" it still seems to be invoking the copy constructor!
    v.emplace_back(42);

    return 0;
}

When I try to compile this code I get the following error:

error C2280: 'Test::Test(const Test &)': attempting to reference a deleted function

This leads to two questions...

  1. Why is it attempting to use the copy constructor even though I explicitly used emplace_back?

  2. How can I get this to compile without errors? The object is movable so according to the standard it should be able to be stored in a std::vector.


Solution

  • Add Test(Test&&) noexcept; and Test& operator=(Test&&) noexcept; then =default them in your cpp file.

    You should probably make Test(const int value) explicit while you are at it.

    In modern gcc/clang at least, you can =default the move ctor in the header. You cannot do this to operator= because it can delete the left hand side pointer, nor the zero argument constructor (something to do with constructing the default deleter?) or destructor (which has to call the default deleter).