Volatile Volatile - 1 month ago 12
C++ Question

An alternative to PIMPL Idiom when you'd like the interface to have all the memory

The purpose of the PIMPL idiom is to hide implementation, including methods, structures, and even sizes of structures. One downside is it uses the heap.

However, what if I didn't want to hide the size requirements of anything. I just wanted to hide methods, the formatting of the structure and the variable names. One way would be to allocate an array of bytes of the perfect size, have the implementation constantly cast that to whatever structure and use that. But manually find the size of the bytes to allocate for the object? And do casts all the time? Obviously not practical.

Is there an idiom or general way of handling this case that is advantageous to PIMPL or opaque pointers.

Answer

A rather different approach could be to rethink the nature of what your objects really represent. In traditional OOP it's customary to think of all objects as self-contained entities that have their own data and methods. Some of those methods will be private to the class because they're just required for that class's own housekeeping, and so these are the kind of thing you usually move the 'impl' of a Pimpl class.

In a recent project I've been favouring the Domain-Driven Design approach where one of the desirables is to separate the data from the logic that does things with it. The data classes then become little more than structs, and the complex logic that previously was hidden in the Pimpl now can go in a Service object that has no state of its own.

Consider a (rather contrived) example of a game loop:

class EnemySoldier : public GameObject
{
public:
    // just implement the basic GameObject interface
    void        updateState();
    void        draw(Surface&);

private:
    std::unique_ptr<EnemySoldierImp>  m_Pimpl;
};
class EnemySolderImpl
{
public:
      // 100 methods of complex AI logic
      // that you don't want exposed to clients

private:
    StateData       m_StateData;
};
void runGame()
{
    for (auto gameObject : allGameObjects) {
        gameObject->updateState();
    }
}

This could be restructured so that instead of the GameObjects managing their data and their program logic, we separate these two things out:

class EnemySoldierData
{
public:
    // some getters may be allowed, all other data only 
    // modifiable by the Service class. No program logic in this class
private:
    friend class EnemySoldierAIService;
    StateData       m_StateData;
};
class EnemySoldierAIService
{
public:
    EnemySoldierAIService() {}

    void updateState(Game& game) {
        for (auto& enemySoldierData : game.getAllEnemySoldierData()) {
            updateStateForSoldier(game, enemySoldierData);
        }
    }

    // 100 methods of AI logic are now here

    // no state variables
};

We now don't have any need for Pimpls or any hacky tricks with memory allocation. We can also use the game programming technique of getting better cache performance and reduced memory fragmentation by storing the global state in several flat vectors rather than needing an array of pointers-to-base-classes, eg:

class Game
{
public:
        std::vector<EnemySoldierData> m_SoldierData;
        std::vector<MissileData>     m_MissileData;
        ...
}

I find that this general approach really simplifies a lot of program code:

  • There's less need for Pimpls
  • The program logic is all in one place
  • It's much easier to retain backwards compatibility or drop in alternate implementations by choosing between the V1 and V2 version of the Service class at runtime
  • Much less heap allocation