Хранение унаследованных специализированных шаблонных типов в векторе общих указателей? - PullRequest
0 голосов
/ 10 мая 2019

Я работаю над классом 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 в векторе ... и все это преобразование и использование указателей допустимо, но выглядит некрасиво и склонно к возможным будущим проблемам.

...