我想简化这段复杂的代码:
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。
我需要保留“基类对象的容器”抽象,因为有时我不知道我正在操作什么类型的设备,只需要一个基地址并需要在所有设备中找到该设备。
您可以在基类中添加模板函数和/或其他函数,以使用正确的参数调用虚函数
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();
(此代码尚未经过测试,甚至尚未编译,因此可能会出现拼写错误)。