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:
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.PLAYER::PLAYER()
, PLAYER::Load()
- this is even more messy since it means converting references to pointers throughout.PLAYER
GAME_IMPL
instead - this loses any benefits of using pimpl in the first place.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();
}