Я работаю над классом Register, который будет использоваться в приложении для виртуальных ПК. Я пытаюсь сделать их как можно более простыми и быстрыми, чтобы их можно было легко создавать по умолчанию и как минимум копировать по умолчанию.
Мои типы регистров будут полагаться на typedef для своих типов:
Register.h
#pragma once
#include <vector>
#include <memory>
typedef std::uint8_t u8;
typedef std::uint16_t u16;
typedef std::uint32_t u32;
typedef std::uint64_t u64;
struct Register {
virtual void* getValue() = 0;
};
template<typename T>
struct Register_ty : Register {
void* data_;
explicit Register_ty(T* data) : data_(data) {}
virtual void* getValue() = 0;
};
template<typename T>
struct Register_t : Register_ty<T> {};
template<>
struct Register_t<u8> : Register_ty<u8> {
explicit Register_t(u8* data) : Register_ty<u8>( data ) {}
void* getValue() override {
return data_;
}
};
template<>
struct Register_t<u16> : Register_ty<u16> {
explicit Register_t(u16* data) : Register_ty<u16>(data) {}
void* getValue() override {
return data_;
}
};
template<>
struct Register_t<u32> : Register_ty<u32> {
explicit Register_t(u32* data) : Register_ty<u32>(data) {}
void* getValue() {
return data_;
}
};
template<>
struct Register_t<u64> : Register_ty<u64> {
explicit Register_t(u64* data) : Register_ty<u64>(data) {}
void* getValue() {
return data_;
}
};
struct Memory {
std::vector<Register*> bank; // vector
};
И когда я иду использовать это как так:
#include <iostream>
#include "Register.h"
int main() {
u8 a = 8;
Register_t<u8> r8(&a);
u16 b = 16;
Register_t<u16> r16(&b);
u32 c = 32;
Register_t<u32> r32(&c);
u64 d = 64;
Register_t<u64> r64(&d);
Memory mem;
mem.bank.push_back( &r8 );
mem.bank.push_back( &r16 );
mem.bank.push_back( &r32 );
mem.bank.push_back( &r64 );
for (auto& b : mem.bank) {
std::cout << b->getValue() << '\n';
}
return EXIT_SUCCESS;
}
Все в этой точке кажется нормальным, и это дает мне возможный вывод:
000000000029F6B4
000000000029F704
000000000029F754
000000000029F7A8
Который дает мне адрес указателей. Однако, когда я пытаюсь разыменовать их так:
for ( auto& b : mem.bank ) {
std::cout << *b->getValue() << '\n';
}
Это не компилируется, поэтому мне пришлось вернуться и переосмыслить свой код ... Я до сих пор придумал это:
#include <iostream>
#include "Register.h"
int main() {
using namespace vpc;
u8* pa = nullptr;
u8 a = 8;
pa = &a;
Register_t<u8> r8(pa);
u16* pb = nullptr;
u16 b = 16;
pb = &b;
Register_t<u16> r16(pb);
u32* pc = nullptr;
u32 c = 32;
pc = &c;
Register_t<u32> r32(pc);
u64* pd = nullptr;
u64 d = 64;
pd = &d;
Register_t<u64> r64(pd);
Memory mem;
mem.bank.push_back( &r8 );
mem.bank.push_back( &r16 );
mem.bank.push_back( &r32 );
mem.bank.push_back( &r64 );
for (auto& b : mem.bank) {
std::cout << b->getValue() << '\n';
}
std::cout << '\n';
for (auto& b : mem.bank) {
auto p = b->getValue();
auto res = reinterpret_cast<u8*>(p);
std::cout << static_cast<u16>(*res) << '\n';
}
return EXIT_SUCCESS;
};
И он дал мне вывод:
00000000001DF694
00000000001DF704
00000000001DF774
00000000001DF7E8
8
16
32
64
Что сейчас дает мне правильные значения. Впервые я смог добиться такого поведения, и мне пришлось использовать pointer manipulation
, чтобы сделать это. Первоначально я вообще не использовал указатели и не использовал void*
в своих классах.
Тогда я не мог просто создать переменную и передать ее по ее адресу, поскольку она тоже не работала, поэтому мне пришлось создавать переменные этих типов в моем основном коде и инициализировать их чем-то, мне нужно было создавать указатели эти типы и инициализировать их с nullptr
, и, наконец, мне пришлось назначить мои указатели на их соответствующие адреса переменных типа.
Затем, чтобы иметь возможность извлекать действительные значения из этих указателей, разыменовывая их, я должен был выполнить некоторые манипуляции с указателями путем приведения, затем я должен был привести этот разыменованный тип.
for (auto& b : mem.bank) {
// create an auto p and get the value; p should be a void*
auto p = b->getValue();
// create an auto type for our result and cast our
// void* p to a u8* {smallest type} and {char} based
auto res = reinterpret_cast<u8*>(p);
// now since the underlying type is char I have to deference my
// res* and static cast it to a u16.
std::cout << static_cast<u16>(*res) << '\n';
}
Этот хак не очень симпатичный или дружелюбный. Пока это работает, но я уже вижу некоторые проблемы раньше времени. Если у меня есть регистр типа u64, и большинство его битов заполнены необходимыми данными, и я переинтерпретирую его указатель на u8 *, разыменяем его и приводим к типу u16
Я собираюсь потерять информацию, однако это единственная комбинация, которая дала мне правильные результаты.
Об этом проекте кода или реализации:
- Есть ли более простой и чистый способ добиться этого?
- Насколько значительна будет потеря данных в этой конкретной реализации?
- Есть ли еще какие-то проблемы, которые я мог бы увидеть, если бы я перебрал их?
Я хотел бы знать, как я могу это сделать, поскольку я еще даже не пытался сохранить эти типы регистров в качестве shared_ptr в векторе ... и все это преобразование и использование указателей допустимо, но выглядит некрасиво и склонно к возможным будущим проблемам.