C++ 中伪造模板虚函数

问题描述 投票:0回答:1

我想简化这段复杂的代码:

class Device {
protected:
  int base;
  int size;
  // other

public:
  virtual uint64_t load(uint64_t addr, uint64_t size) = 0;
  virtual void store(uint64_t addr, uint64_t value, uint64_t size) = 0;
  // other
};

class Dram : protected Device {
private:
  std::array<uint8_t, 1024> data;

public:
  uint64_t load(uint64_t addr, uint64_t size) override
  {
    ram_specific_load();

    switch (size) {
    case 8: return data[addr];
    case 16: return *reinterpret_cast<uint16_t*>(data.data() + addr);
    case 32: return *reinterpret_cast<uint32_t*>(data.data() + addr);
    case 64: return *reinterpret_cast<uint64_t*>(data.data() + addr);
    default: return 0;
    }
  }

  void store(uint64_t addr, uint64_t value, uint64_t size) override
  {
    ram_specific_store();

    switch (size) {
    case 8:
        data[addr] = value;
        break;
    case 16:
        *reinterpret_cast<uint16_t*>(data.data() + addr) = value;
        break;
    case 32:
        *reinterpret_cast<uint32_t*>(data.data() + addr) = value;
        break;
    case 64:
        *reinterpret_cast<uint64_t*>(data.data() + addr) = value;
        break;
    default:
        break;
    }
  }

  // other
};

// other devices

然后就这样使用了:

std::vector<Device*> devices;

devices.push_back(&Dram());
devices.push_back(&Gpu());
...

devices[1]->store(0x1234, devices[0]->load(0x4321, 64), 64);

uint32_t var = devices[1]->load(0x1212, 32);

我尝试重载operator[]而不是使用加载/存储函数,但事实证明有些设备需要区分加载/存储操作来调用附加函数。

uint64_t& operator[](uint64_t addr)
{
  if (is_loading())
    foo1();
  else
    foo2();
  
  return data[addr];
}

我尝试将它们设为模板函数来处理数据大小,但它们不能同时是模板函数和虚拟函数。这些函数应该是虚拟的,以免破坏设备继承的抽象。

我尝试使用 CRTP,但这也使得设备不太兼容 Base-class。

我需要保留“基类对象的容器”抽象,因为有时我不知道我正在操作什么类型的设备,只需要一个基地址并需要在所有设备中找到该设备。

c++ templates operator-overloading crtp
1个回答
0
投票

您可以在基类中添加模板函数和/或其他函数,以使用正确的参数调用虚函数

class Device {
protected:
  int base;
  int size;
  // other

public:
  virtual uint64_t load(uint64_t addr, uint64_t size) = 0;
  virtual void store(uint64_t addr, uint64_t value, uint64_t size) = 0;
  // other

  // templates
  template<class T> 
  uint64_t 
  load(uint64_t addr)
  {
    return static_cast<T>(this->load(addr, sizeof(T)*CHAR_BIT);
  }

  template<class T> 
  void 
  store(uint64_t addr, T value)
  {
    this->store(addr, static_cast<uint64_t>(value), sizeof(T)*CHAR_BIT);
  }

  // Non-template functions
  uint64_t 
  load64(uint64_t addr)
  {
    return this->load(addr, 64);
  }

  void 
  store64(uint64_t addr, uint64_t value)
  {
    this->store(addr, value, 64);
  }
};

现在你的例子看起来像这样

devices[1]->store(0x1234, devices[0]->load<uint64_t>(0x4321));
devices[1]->store64(0x1234, devices[0]->load64(0x4321));

如果您想使用数组访问语义,那么您可以使用代理来推迟正确的加载/存储操作(如 @Jarod42 建议)

class Device {
  // all of the above

  struct Proxy {
    Device* dev;
    uint64_t addr;

    // The proxy uses the template load/store

    template<class T> 
    operator T() 
    {
      return this->dev->load<T>(this->addr);
    }

    template<class T> 
    Proxy 
    operator=(T value)
    {
      this->dev->store(this->addr, value);
      return *this;
    }

  };

  Proxy 
  operator[](uint64_t addr) {
    return Proxy{this, addr};
  }
};

代理不会知道您要访问内存的类型,直到您将其存储在具有正确类型的临时变量中,因此除非提供额外的函数,否则无法完成单行示例

uint64_t tmp = (*devices[0])[0x4321];
(*devices[1])[0x1234] = tmp;

这可以通过向代理添加功能来解决

struct Proxy {
  // All of the above

  uint64_t
  as64()
  {
    return *this; // Invokes the conversion operator
  }
};

最后,这个例子看起来像这样

(*devices[1])[0x1234] = (*devices[0])[0x4321].as64();

(此代码尚未经过测试,甚至尚未编译,因此可能会出现拼写错误)。

© www.soinside.com 2019 - 2024. All rights reserved.