2

I have a class implemented using the PImpl Ideom:

class FooImpl {};

class Foo
{
   unique_ptr<FooImpl> myImpl;
public:
   Foo();
   ~Foo();
};

And now I want to put this into a std::vector

void Bar()
{
   vector<Foo> testVec;
   testVec.resize(10);
}

But when I do that, I get a compiler error (VC++ 2013)

error C2280: 'std::unique_ptr>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' : attempting to reference a deleted function

I get the same error with testVec.emplace_back(); and testVec.push_back(std::move(Foo()));

(As a workaround, using vector<unique_ptr<Foo>> seems to work, but I don't understand why the code above doesn't work.)

Working example: http://coliru.stacked-crooked.com/a/b274e1209e47c604

Eliad
  • 894
  • 6
  • 20
Niki
  • 15,662
  • 5
  • 48
  • 74

2 Answers2

2

Since std::unique_ptr is not copyable, class Foo does not have a valid copy constructor.

You could either deep copy or use a move constructor:

#include <memory>
#include <vector>

class FooImpl {};

class Foo
{
   std::unique_ptr<FooImpl> myImpl;
public:
   Foo( Foo&& f ) : myImpl( std::move( f.myImpl ) ) {}
   Foo(){}
   ~Foo(){}
};

int main() {
    std::vector<Foo> testVec;
    testVec.resize(10);
    return 0;
}

Live example: https://ideone.com/HYtPMu

Community
  • 1
  • 1
m.s.
  • 16,063
  • 7
  • 53
  • 88
  • But why do resize or emplace_back have to copy in the first place? – Niki Jun 18 '15 at 07:51
  • 1
    @nikie, make the assignment and copy constructor private, and you'll get a template instantiation error showing the full path of how they get called. `resize` wants `erase`, `erase` wants `_Move`, '_Move` uses assignment operator. – Rudolfs Bundulis Jun 18 '15 at 07:56
  • @m.s. btw the provided example still does not compile in VC, and righlty so. The `resize` call wants an assignment operation, not move constructor, possibly a difference in VC/gcc/clang. Since the op is clearly using VC this is not a solution. – Rudolfs Bundulis Jun 18 '15 at 08:15
2

So what happens is that the vector template tries to access the copy constructor of the Foo class. You have not provided one, so the compiler tries to generate a default implementation that calls the copy constructor on all members. Since the std::unique_ptr does not have a copy constructor from another std::unique_ptr (which is logical because it does not know how to copy the object) the compiler cannot generate the assignment operator for Foo and it fails. So what you can do is provide a copy constructor for the Foo class and decide how to handle the pointer:

#include <memory>
#include <vector>

using namespace std;
class FooImpl {};

class Foo
{
    unique_ptr<FooImpl> myImpl;
public:
    Foo()
    {
    }
    ~Foo()
    {
    }
    Foo(const Foo& foo)
    {
        // What to do with the pointer?
    }
    Foo& operator= (const Foo& foo)
    {
        if (this != &foo)
        {
            // What to do with the pointer?
        }
        return *this;
    }
};

int main(int argc, char** argv)
{
    vector<Foo> testVec;
    testVec.resize(10);
    return 0;
}
Rudolfs Bundulis
  • 11,636
  • 6
  • 33
  • 71
  • 2
    Implementation of a copy constructor reusing assignment operator seems a bad idea, because copy constructor is called on uninitialized object, so if you call assignment operator with the uninitialized object as left-hand side argument, you get undefined behavior. Assignment operator expects initialized object as left-hand side argument. – Serge Rogatch Jun 18 '15 at 07:56
  • @SergeRogatch ok this seems interresting, before C++11 I've been using this pattern, can you elaborate a bit more? I understand the logic, but as far as I only assign member variables isn't it fine? I mean doesn't the copy ctor call all the member ctors before as well? Can you please give me a sample of undefined behavior? I'de be interested to understand the consequences. – Rudolfs Bundulis Jun 18 '15 at 07:59
  • You are right, copy constructor will call default constructors of member variables before entering { if they are not initialized by the user in the copy constructor initialization list: http://stackoverflow.com/a/754754/1915854 .But I think that raw pointers (e.g. void*) and variables of primitive data types (e.g. int) will contain garbage. That is allowed in a copy constructor, because it is called on uninitialized object. But assignment operator expects initialized object as left-hand side. Assignment operator may delete a raw pointer (void*), and that would corrupt memory for uninitialized – Serge Rogatch Jun 18 '15 at 08:49
  • @SergeRogatch ok, I got your point, I was thinking there was something more dreadful. As for the raw pointer members - well that is up to the user to do a proper initializer list and null them and check the values in the assignment operator, that is at least what I would do. Thanks for the explanation of your statement:) But I'll still stick with my belief that in case if the code is written correctly it is fine. Another thing is duplicate operations that can possibly overlap in the initializer list and in the assignment operator, but that is another story. – Rudolfs Bundulis Jun 18 '15 at 09:02