1

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.
c z
  • 7,726
  • 3
  • 46
  • 59
  • Just forward declare `PLAYER` and write function definitions after all classes that they rely on - not in class definition. – ALX23z Sep 20 '21 at 11:10
  • No, there is no "way to allow the `_impl` pointer to be set before" the object it's pointing to is fully constructed, by the `new` operator whose result gets assigned to the `_impl` pointer. This is fundamental to C++. C++ simply does not work any other way. You will need to redesign how your classes work, and their mutual relationships, in order to make it fit within this design pattern. – Sam Varshavchik Sep 20 '21 at 11:10
  • `GAME_IMPL(GAME& game)` is suspicious IMO. – Jarod42 Sep 20 '21 at 11:18
  • @TedLyngmo: OP's problem is not compilation, but access to not initalized pointer, as `this` (from `GAME`) is still in construction. Modified [Demo](https://godbolt.org/z/5sdbq8dcf) with non empty struct. – Jarod42 Sep 20 '21 at 11:20
  • @Jarod42 I'm open to alternatives! This is essentially to skirt around using a singleton, but a singleton wouldn't fix the issue either. – c z Sep 20 '21 at 11:25
  • For me, your first alternative is the right way (So `PLAYER(const Images&)`). – Jarod42 Sep 20 '21 at 11:30
  • @Jarod42 Aha, I misunderstood "_you can see that `_sprite.load( game.getImages() )` will fail_" – Ted Lyngmo Sep 20 '21 at 11:30
  • 1
    Your alternative is: a proper pimpl has the public object serving as a public interface to the object, ***and nothing more***. Full stop. There is never a reason for the implementation object to have any need to access its public instance. Try to figure out how to redesign your classes so that implementation objects have no need to have a reference to their public instances – Sam Varshavchik Sep 20 '21 at 12:27

2 Answers2

2

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();
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • You can make `GAME_IMPL` [`final`](https://en.cppreference.com/w/cpp/language/final). It might allow compiler to devirtualize some call. (For `GAME`, it is most an OP's decision). – Jarod42 Sep 20 '21 at 12:11
  • @Jarod42 Thanks for the cleanup :) I also could add some more explicit comments on what should go in what header file/source file if the OP so wishes. – Pepijn Kramer Sep 20 '21 at 12:14
  • I'm aware the original post didn't compile, it was just to show what was going on. Using a virtual interface seems quite clean and I can't imagine the vtable overhead is any worse than the dereferenceing overhead. – c z Sep 20 '21 at 13:37
  • @cz and as jarod42 said, adding final to impl might optimize that vtable indirection away too – Pepijn Kramer Sep 20 '21 at 14:20
1

P Kramer's answer is the good answer to the problem presented in my case.

However if someone ends up here via a Google search for the title alone "Passing a partially constructed object", I did find that this is possible. Essentially the nested class (GAME_IMPL in this case) must assign its own pointer in the parent class (GAME in this case), i.e.

explicit GAME_IMPL( GAME& game ) : _player( (game._impl.reset( this ), game) ) { }

Now GAME::_impl is assigned during PLAYER::PLAYER and GAME::getImages() can be used.

This is messy for the pimpl case, but may solve similar issues.

c z
  • 7,726
  • 3
  • 46
  • 59
  • +1 for a technically correct answer, but indeed don't use it :) The messiness is not only the syntax used in the constructor, but also in now having to know exactly how `GAME`, `GAME_IMPL` and `PLAYER` work to know if everything necessary for the construction of `_player` is correctly initialized. – G. Sliepen Sep 20 '21 at 14:30