Search code examples
c++templatesvirtual

Avoiding virtual functions on embedded target


I have a class Player that plays back data from a big block of memory that consists of a number of equal chunks.

typedef char chunk_t[100];

typedef struct {
    chunk_t data[100]
} blockOfMemory_t;

The Player itself could theoretically work for different layouts and contents of data, so I would like to program it in a re-usable way. To do this, I thought of something like this:

class Player {
public:
    Player() { ... }
    virtual ~Player() { ... }

    void play() 
    {
        for (int i = 0; i < getNumChunks(); i++)
        {
           if (chunkHasX(i) || chunkHasY(i))
               playChunk(i);
        }
    }

protected:
    virtual int getNumChunks() = 0;
    virtual bool chunkHasX(int chunkIndex) = 0;
    virtual bool chunkHasY(int chunkIndex) = 0;
    virtual void playChunk(int chunkIndex) = 0;
}

By inheriting from this and implementing the data details in the child, I could achieve the re-usability.

However, the target is an ARM Cortex-M4 processor and speed is highly important. For this reason, I would expect to have performance drawbacks when using virtual functions. So I'm looking for a way to achieve the same type of re-usability with an aproach that can be resolved at compile-time and would allow for inlining of chunkHasX(..), etc.

This is screaming "template" - but how would I do that?

Thank you!


Solution

  • I'm going to assume that you have measured and confirmed that the cost of virtual function calls or increased object size makes it desirable to do this. Or perhaps you just think a template design is preferable.

    Inheritance with CRTP

    If you want to use inheritance you could use the Curiously recurring template pattern (CRTP). You have a templated Player base class where the template parameter is the derived class:

    template<class Derived>
    class Player {
    public:
        void play() 
        {
            auto& derived = static_cast<Derived&>(*this);
            
            for (int i = 0; i < derived.getNumChunks(); i++)
            {
               if (derived.chunkHasX(i) || derived.chunkHasY(i))
                   derived.playChunk(i);
            }
        }
    };
    
    class DerivedPlayer : public Player<DerivedPlayer> {
    private:
      friend class Player<DerivedPlayer>;
      int getNumChunks();
      bool chunkHasX(int chunkIndex);
      bool chunkHasY(int chunkIndex);
      void playChunk(int chunkIndex);
    };
    
    int main() {
        DerivedPlayer p;
        p.play();
    }
    

    Live demo.

    Composition

    Or perhaps you could use composition instead of inheritance and compose your Player of a ChunkHolder which is passed as a template parameter:

    template<class ChunkHolder>
    class Player {
    private:
        ChunkHolder chunk_holder;
    public:
        void play() 
        {   
            for (int i = 0; i < chunk_holder.getNumChunks(); i++)
            {
               if (chunk_holder.chunkHasX(i) || chunk_holder.chunkHasY(i))
                   chunk_holder.playChunk(i);
            }
        }
    };
    
    class MyChunkHolder {
    public:
      int getNumChunks();
      bool chunkHasX(int chunkIndex);
      bool chunkHasY(int chunkIndex);
      void playChunk(int chunkIndex);
    };
    
    int main() {
        Player<MyChunkHolder> p;
        p.play();
    }
    

    Live demo.

    Update: Russ Schultz comment reminded me that if you want to treat these different Players polymorphically, you can. Just introduce an interface:

    class IPlayer {
    public:
      virtual ~IPlayer(){}
      virtual void play() = 0;
    };
    

    And then in both cases you can inherit from this interface and override the play() function:

    template<class T>
    class Player : IPlayer {
    public:
        void play() override;
    };
    

    Now you can, for example, put the players in one container but you don't compromise performance by calling virtual function in inner loops. Live demo with CRTP and composition.