避免在嵌入式目标上使用虚函数

Avoiding virtual functions on embedded target

本文关键字:函数 目标 嵌入式      更新时间:2023-10-16

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

typedef char chunk_t[100];
typedef struct {
    chunk_t data[100]
} blockOfMemory_t;

播放器本身理论上可以为不同的布局和数据内容工作,所以我想以可重用的方式编程。为了做到这一点,我想到了这样的东西:

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(..)等。

这是尖叫"模板"-但我怎么做呢?

谢谢!

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

与CRTP的继承

如果您想使用继承,您可以使用好奇循环模板模式(CRTP)。你有一个模板化的Player基类,其中模板参数是派生类:

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

现场演示。

作文

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

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

现场演示。

更新:Russ Schultz的评论提醒我,如果你想以多态的方式对待这些不同的玩家,你可以这么做。只需引入一个接口:

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

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

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

现在你可以,例如,把播放器放在一个容器中,但你不会因为在内循环中调用虚函数而影响性能。

与静态函数相比,调用虚函数的代价最多是一次查找。

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

ldr [r0,#8],r4 
blx r4

代替

ldr #0x400025e5,r4
blx r4

(如果需要分支的地址可以内联编码)
br #0x1035

只要不是在紧循环中调用虚函数,这就不是问题。