2

I am using the pimpl idiom with a const std::unique_ptr to hold the class implementation. My class needs to support copy construction and copy assignement. What I'd like to do is manually call the copy constructor of the impl class inside the unique_ptr. However, I fail to see how.

#include <memory>

struct potato {
    potato();
    ~potato();
    potato(const potato& p);

private:
    struct impl;
    const std::unique_ptr<impl> _pimpl;
};

struct potato::impl {
    int carbs = 42;
};

potato::potato()
        : _pimpl(std::make_unique<impl>()) {
}

potato::~potato() = default;

potato::potato(const potato& p) {
    // Try to call the copy constructor of impl, stored in unique_ptr, not the
    // unique_ptr copy-constructor (which doesn't exist).
    _pimpl.get()->impl(p._pimpl); // This doesn't work.
}

I've checked out another question about explicitly calling the copy-constructor on an object. One answer recommended using placement new.

Object dstObject;
new(&dstObject) Object(&anotherObject);

Could I use this in my copy-constructor? If so, how? I don't really understand whats happening there. Thank you.

scx
  • 3,221
  • 1
  • 19
  • 37
  • 1
    You cannot call a constructor. Constructors are automatically and only called when creating a new object. – François Andrieux Dec 07 '18 at 21:38
  • `new(&dstObject) Object(&anotherObject);` is placement `new` and would require you to first manually call the destructor of `dstObject`. And then if your construction fails, you are pretty screwed. – François Andrieux Dec 07 '18 at 21:39
  • 1
    It cannot have a default copy constructor, of course it can have a custom copy constructor! – Matthieu Brucher Dec 07 '18 at 21:42
  • 3
    @SombreroChicken why are you saying this? It's a matter of calling copy-ctor for the contained object. – SergeyA Dec 07 '18 at 21:44
  • When using assignment operator, one has to keep in mind the potential for self-assignment. Using placement new as François described could run into self-copy-construction... that atypical situation (were it to occur) could make for some fun debugging. – Eljay Dec 07 '18 at 21:55
  • Does the `unique_ptr` have to be `const`? – Galik Dec 07 '18 at 21:59
  • 1
    Quick tip: if you use the pimpl idiom, you may freely 'inherit' `unique_ptr`'s move semantics. Just remove `const` from `_pimpl` and add defaulted move assignment and ctor definitions in your source. – Julien Villemure-Fréchette Dec 07 '18 at 22:30

2 Answers2

6

What I'd like to do is manually call the copy constructor of the impl class inside the unique_ptr

Here lies your error. As you are inside the (copy) constructor of potato, there's no impl object already constructed over which you'd have to "manually" invoke the copy constructor.

Just construct your new impl passing to it a reference to the original impl to copy.

potato::potato(const potato& p)
    : _pimpl(std::make_unique<impl>(*p._pimpl) {
}

As for assignment, you can easily forward to the impl assignment operator:

potato &operator=(const potato &p) {
    *_pimpl = *p._pimpl;
    return *this;
}
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • @scx oops, misread the question as asking about home-work assignment. – SergeyA Dec 07 '18 at 21:45
  • 2
    @scx: of course not. Assignment has an already-constructed object on the left hand, so it's a completely different beast. Still, you don't need to do anything fancy: you just need to forward the operation to the underlying `_impl`, so just do something like `*_pimpl = *p._pimpl; return *this;`. – Matteo Italia Dec 07 '18 at 21:46
  • Then again, of course with a PIMPL class you can always transform a wrapper class assignment to a "destroy _pimpl + copy construct a new one", but the semantics is slightly different (and is more wasteful in terms of allocations). – Matteo Italia Dec 07 '18 at 21:50
  • I doesn't work since the unique_ptr is const. I'll have to figure out something else for assignment. – scx Dec 07 '18 at 21:51
  • 2
    @scx: forwarding assignment does work, the `const`ness of the `unique_ptr` doesn't affect the `const`ness of the returned reference... it just blocks reassignment (and some other stuff) _of the `unique_ptr` itself_, not of the pointed object. – Matteo Italia Dec 07 '18 at 21:55
  • @MatteoItalia Thanks for the really complete answer! – scx Dec 08 '18 at 16:15
0

You can invoke constructors explicitly on uninitialized storage with the placement new operator, as you mentioned. You can return the storage of an object to an uninitialized state by explicitly calling its destructor.

Here’s a simple implementation of the assignment operator that explicitly invokes the copy constructor and the destructor you already defined as part of your interface:

#include <new>

potato& potato::operator=(const potato& x)
{
  if ( this != &x ) { // check for self-assignment!
    this->~potato();
    new(this) potato(x);
  }

  return *this;
}

You would probably also want to define a move constructor and overload the assignment operator when the right-hand side is a temporary. That is, overload for potato&& src as well as const potato& src. You could use the swap idiom if your class supports it, or the same code as above, but calling new(this) potato(std::move(src));.

If you have access to the destructor and copy constructor of the class wrapped in a smart pointer, you can do the same trick with them, just dereferencing the smart pointers. You probably don’t want to, though.

The default copy constructor and assignment operator ought to work just fine, if the contents of the class are smart pointers, STL containers, and so on. You probably would want to copy the data referenced by the smart pointers by writing things like *p = x or *p = *q or std::swap, rather than explicit calls to the copy constructor.

Davislor
  • 14,674
  • 2
  • 34
  • 49