Search code examples
c++pimpl-idiom

Passing the partially constructed object in pimpl


I have a class setup that I have converted to use pimpl, something like this:

(outline only, I'm aware this doesn't compile)

struct GAME
{
    unique_ptr<GAME_IMPL> _impl; 

    explicit GAME() : _impl( new GAME_IMPL( *this ) );
    IMAGES getImages() { return _impl._images; }
};

struct GAME_IMPL
{
    IMAGES _images;
    PLAYER _player;

    explicit GAME_IMPL( GAME& game ) : _player( game ) { }
};

struct PLAYER
{
    SPRITE _sprite;

    explicit PLAYER( GAME& game ) { _sprite.load( game.getImages() ); }
};

Before I converted to a pimpl pattern, this was fine. _sprite.load( game.getImages() ) could access GAME::_images because GAME::_images instantiates before GAME::_player.

However, after converting to pimpl, you can see that _sprite.load( game.getImages() ) will fail - PLAYER cannot access GAME::_images, because GAME::getImages uses GAME::_impl, which is not assigned until after all of GAME_IMPL has been constructed.

Obviously my example is contrived. But is there some way to allow the _impl pointer to be set before or while the GAME_IMPL object is still being constructed?

I considered the following:

  • Passing PLAYER only the necessary elements. This is messy since it involves modifying the API of PLAYER::PLAYER every time I adjust PLAYER's fields. Also in practice PLAYER's constructor may end up taking 30 odd parameters.
  • Using a two-stage initialisation, e.g. PLAYER::PLAYER(), PLAYER::Load() - this is even more messy since it means converting references to pointers throughout.
  • Passing PLAYER GAME_IMPL instead - this loses any benefits of using pimpl in the first place.

Solution

  • I think you missed some details, here is a setup that compiles.

    #include <memory>
    
    struct GAME_IMPL;
    struct IMAGES {};
    
    // A 'trick' I often use
    // for pimpl you can define an abstract base
    // helps you check if public and impl class
    // implement the same functions
    //
    // it also ensures you only have a pointer to an interface
    // of your implementation in your public header file
    
    struct GAME_ITF
    {
        virtual ~GAME_ITF() = default;
        virtual IMAGES get_Images() = 0;
    protected:
        GAME_ITF() = default; // prevent accidental instantiation.
    };
    
    
    // in pimpl, game is just forwarding to game_impl
    struct GAME : public GAME_ITF
    {
        std::unique_ptr<GAME_ITF> _impl;
    
        GAME();
        virtual ~GAME() = default;
    
        IMAGES get_Images() override
        {
            return _impl->get_Images();
        }
    };
    
    struct SPRITE
    {
        void load(IMAGES) { /*..*/ }
    };
    
    struct PLAYER
    {
        SPRITE _sprite;
    
        explicit PLAYER(GAME_ITF& game)
        { 
            _sprite.load(game.get_Images());
        }
    };
    
    struct GAME_IMPL : public GAME_ITF
    {
        IMAGES _images;
        PLAYER _player;
    
        GAME_IMPL() : 
            _player{*this} // <=== player can refer to the implementation's interface (no impl. details needed either)
        {
        }
    
        IMAGES get_Images() override
        {
            return IMAGES{};
        }
    };
    
    GAME::GAME() :
        _impl(std::make_unique<GAME_IMPL>())
    {
    }
    
    int main()
    {
        GAME game;
        IMAGES images = game.get_Images();
    }