避免嵌入式目标上的虚函数

Avoiding virtual functions on embedded target

我有一个 class Player 可以从由许多相等块组成的大内存块中播放数据。

typedef char chunk_t[100];

typedef struct {
    chunk_t data[100]
} blockOfMemory_t;

Player 本身理论上可以适用于不同的布局和数据内容,因此我想以可重用的方式对其进行编程。为此,我想到了这样的事情:

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;
}

通过继承并在子进程中实现数据细节,我可以实现可重用性。

但是,目标是 ARM Cortex-M4 处理器,速度非常重要。出于这个原因,我预计在使用虚函数时会出现性能缺陷。所以我正在寻找一种方法来实现相同类型的可重用性,方法是可以在编译时解决并允许内联 chunkHasX(..)

这太尖叫了 "template" - 但我该怎么做呢?

谢谢!

调用虚函数与​​静态函数的成本最差是单次查找。

每个对象都有一个指向它的 vtable 的指针,其中包含虚函数指针,所以它类似于:

ldr [r0,#8],r4 
blx r4

而不是

ldr #0x400025e5,r4
blx r4

或(如果您需要跳转到的地址可以内联编码)

br #0x1035

只要您不在紧密循环中调用虚函数,就完全没有问题。

我假设您已经测量并确认虚函数调用的成本或增加的对象大小使得这样做是可取的。或者您只是认为模板设计更可取。

继承 CRTP

如果你想使用继承,你可以使用 Curiously recurring template pattern (CRTP)。您有一个模板化的 Player 基础 class,其中模板参数是派生的 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.

作文

或者您可以使用组合而不是继承,将 Player 组合成作为模板参数传递的 ChunkHolder

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.

更新: Russ Schultz 的评论提醒我,如果你想多态地对待这些不同的玩家,你可以。随便介绍一个接口:

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

然后在这两种情况下,您都可以继承此接口并覆盖 play() 函数:

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

例如,现在您可以将播放器放在一个容器中,但不会通过在内部循环中调用虚函数来降低性能。 CRTP and composition.

现场演示