Search code examples
c++argument-passingpimpl-idiom

Pass pimpl argument to a pimpl object


I've two classes that implement PIMPL: State and StateMachine.

They implement Private::State and Private::StateMachine.

Private::StateMachine has an "addState(Private::State &)" method. I want to write the addState() method into its PIMPL class, in order to do

StateMachine machine;
State        state;

machine.addState(state);

How can I pass to Private::StateMachine inside StateMachine the object Private::State pimpled in State?

StateMachine.h

#include <memory>

class State;
namespace Private {
  class StateMachine;
} // namespace Private

class StateMachine
{
public:
  StateMachine();
  ~StateMachine();

  // want to add this
  void addState(const State &state);

private:
  std::unique_ptr<Private::StateMachine> m_stateMachine;
};

State.h

#include <memory>

namespace Private {
class State;
} // namespace Private

namespace StateMachine {

class State
{
public:
  State();
  ~State();

private:

  std::unique_ptr<Private::State> m_state;
};

Solution

  • There are probably an unlimited number of answers to this question.

    Here are some questions to guide your thinking:

    1. What should be the effect of copying a State object? Should the internal Private::State be copied also?
    2. Should copies be disallowed (preferring moves)? In which case you'll want to dump user-defined constructors allowing the compiler to generate moves for you.
    3. Assuming (2), should your Private::StateMachine be storing a unique_ptr<Private::State> or a unique_ptr<State>? This gets tricky because if you want direct interaction between state and machine behind the pimpl you probably want to store a unique_ptr<Private::State> to avoid exposing private operations in the ::State interface. If you ever want to deliver a State back to user code then you'll have to rebuild the ::State handle. This might argue for a non-owning version of ::State.
    4. What is the intent of the use of pimpl? Is the idea that State is a handle to a Private::State? Is it reasonable to have the concept of a 'null handle' (for example as a result of a move)? Should 2 handles share the same state during a copy (argues for shared_ptr)? and so on.

    In summary, there is an answer but it depends entirely on the synopsis of StateMachine, State and their desired interaction in user code. I would suggest that you start there - design the use cases and observe their effects on copy, move, assignment etc. This should lead you to a natural implementation.