Было бы полезно использовать союз? - PullRequest
2 голосов
/ 28 сентября 2019

Работая над своими уроками, я начал экспериментировать с некоторыми проектными решениями, которые я мог бы включить в свой существующий Register класс.Я думал о объединении соответствующих std::uint8_t, std::uint16_t, std::uint32_t и или std::uint64_t во вложенную безымянную структуру данных struct - union с std::bitset<value> в моем шаблонном классе в зависимости от типа шаблона класса при создании экземпляра, гдеvalue - это размер битов этого типа.


Вот произвольный класс, над которым я работал, чтобы включить дизайн структуры данных.Он использует те же файлы заголовков, которые использует мой класс Register, которые можно найти ниже.

template<typename T>
struct MyRegT {
    union {
        struct {
            T value : (sizeof(T) * CHAR_BIT);
        };
        std::bitset<sizeof(T) * CHAR_BIT> bits;
    } reg;

    explicit MyRegT(T value) : reg{ value } {}
};

Оба класса компилируются и сообщают об одинаковых ожидаемых значениях.Единственное различие между ними состоит в том, что вышеприведенный класс использует меньше внутренней памяти из-за объединения, в котором может быть хорошая вещь.

Однако при использовании union и битовых полей нужно быть осторожным из-за множества различных факторов, таких как порядковый номер архитектуры, сама операционная система, компилятор в том, как он дополняет память в структурах.

В этом конкретном случае, каковы были бы последствия, если бы я включил это;Будет ли это проблемой с переносимостью, безопасностью потоков или поддержкой потоков, поддерживает ли она читабельность и т. д.?

Это всего лишь проблемы, с которыми я сталкиваюсь, прежде чем изменить свой существующий класс.Я уже упоминал о преимуществах повторного использования той же памяти, и в этом контексте я считаю, что все должно быть в порядке, как в конкретном случае использования:

MyRegT<std::uint8_t> r8{32};

Здесь объединение будет между std::uint8_t и std::bitset<8> и значение будет содержать 32, тогда как bitset сможет предоставить пользователю string текущих хранимых битов или двоичное представление значения 32, сохраненного как std::uint8_t, а также любое другоеиз других функций, которые std::bitset может предложить.Любые изменения в одном должны отражаться в другом.Так как они являются взаимно одной и той же сущностью, это просто разные представления об этом.

Если бы я использовал что-то в этих строках, было бы лучше иметь std::bitset<...> внутри безымянной структуры и std::uintN_t, где N - это размер в битах вне структуры?

Я хотел бы знать, есть ли другие недостающие плюсы и все минусы в этом подходе, прежде чем я решу внести какие-либо серьезные изменения в мою уже существующую кодовую базу.


Вотмой полный существующий класс Register со всеми его текущими функциональными возможностями для полной справки.

Register.h

#pragma once

#include <algorithm>
#include <bitset>
#include <cassert>
#include <climits>
#include <cstdint>
#include <exception>
#include <iterator>
#include <iostream>
#include <iomanip>
#include <limits>
#include <type_traits>

namespace vpc {
    using u8  = std::uint8_t;
    using u16 = std::uint16_t;
    using u32 = std::uint32_t;
    using u64 = std::uint64_t;

    template<typename T>
    struct Register;

    using Reg8  = Register<u8>;
    using Reg16 = Register<u16>;
    using Reg32 = Register<u32>;
    using Reg64 = Register<u64>;

    template<typename T>
    struct Register {
        T value;
        std::bitset<sizeof(T)* CHAR_BIT> bits;

        Register() : value{ 0 }, /*previous_value{ 0 },*/ bits{ 0 } {}

        template<typename U, std::enable_if_t<(sizeof(U) > sizeof(T))>* = nullptr>
        explicit Register(const U val, const u8 idx = 0) :
            value{ static_cast<T>((val >> std::size(bits) * idx) &
                  std::numeric_limits<std::make_unsigned_t<T>>::max()) },
            bits{ value }
        {
            constexpr u16 sizeT = sizeof(T);
            constexpr u16 sizeU = sizeof(U);
            assert((idx >= 0) && (idx <= ((sizeU / sizeT) - 1)) );
        }

        template<typename U, std::enable_if_t<(sizeof(U) < sizeof(T))>* = nullptr>
        explicit Register(const U val, const u8 idx = 0) :
            value{ static_cast<T>((static_cast<T>(val) << sizeof(U)*CHAR_BIT*idx) &
                  std::numeric_limits<std::make_unsigned_t<T>>::max()) },
            bits{ value }
        {
            constexpr u16 sizeT = sizeof(T);
            constexpr u16 sizeU = sizeof(U);
            assert((idx >= 0) && (idx <= ((sizeT / sizeU) - 1)) );
        }

        template<typename U, std::enable_if_t<(sizeof(U) == sizeof(T))>* = nullptr>
        explicit Register(const U val, const u8 idx = 0) :
            value{ static_cast<T>( val ) }, bits{ value }
        {}

        template<typename... Args>
        Register(Args... args) {}

        template<typename U>
        Register(const Register<U>& reg, const u8 idx = 0) : Register(reg.value, idx) {}

        void changeEndian() {
            T tmp = value;
            char* const p = reinterpret_cast<char*>(&tmp);
            for (size_t i = 0; i < sizeof(T) / 2; ++i)
                std::swap(p[i], p[sizeof(T) - i - 1]);
            bits = tmp;
        }

        Register& operator=(const Register& obj) {
            this->value = obj.value;
            //this->previous_value = obj.previous_value;
            this->bits = obj.bits;
            return *this;
        }

        template<typename Lhs, typename Rhs>
        friend auto operator+(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Lhs, typename Rhs>
        friend auto operator-(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Lhs, typename Rhs>
        friend auto operator*(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Lhs, typename Rhs>
        friend auto operator/(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Lhs, typename Rhs>
        friend auto operator%(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Lhs, typename Rhs>
        friend auto operator&(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Lhs, typename Rhs>
        friend auto operator|(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Lhs, typename Rhs>
        friend auto operator^(const Register<Lhs>& l, const Register<Rhs>& r);

        template<typename Reg>
        friend auto operator~(const Register<Reg>& l);
    };

    template<typename Lhs, typename Rhs>
    auto operator+(const Register<Lhs>& l, const Register<Rhs>& r) {    
        return Register<decltype(l.value + r.value)>{ l.value + r.value };
    }

    template<typename Lhs, typename Rhs>
    auto operator-(const Register<Lhs>& l, const Register<Rhs>& r) {
        return Register<decltype(l.value - r.value)>{ l.value - r.value };
    }

    template<typename Lhs, typename Rhs>
    auto operator*(const Register<Lhs>& l, const Register<Rhs>& r) {
        return Register<decltype(l.value * r.value)>{ l.value * r.value };
    }

    template<typename Lhs, typename Rhs>
    auto operator/(const Register<Lhs>& l, const Register<Rhs>& r) {
        if (r.value == 0)
            throw std::exception( "Division by 0\n" );
        return Register<decltype(l.value / r.value)>{ l.value / r.value };
    }

    template<typename Lhs, typename Rhs>
    auto operator%(const Register<Lhs>& l, const Register<Rhs>& r) {
        return Register<decltype(l.value % r.value)>{ l.value % r.value };
    }

    template<typename Lhs, typename Rhs>
    auto operator&(const Register<Lhs>& l, const Register<Rhs>& r) {
        return Register<decltype(l.value & r.value)>{ l.value & r.value};
    }

    template<typename Lhs, typename Rhs>
    auto operator|(const Register<Lhs>& l, const Register<Rhs>& r) {
        return Register<decltype(l.value | r.value)>{ l.value | r.value};
    }

    template<typename Lhs, typename Rhs>
    auto operator^(const Register<Lhs>& l, const Register<Rhs>& r) {
        return Register<decltype(l.value ^ r.value)>{ l.value ^ r.value };
    }

    template<typename Reg>
    auto operator~(const Register<Reg>& r) {
        return Register<decltype(~r.value)>{~r.value};
    }

    template<typename T>
    std::ostream& operator<<(std::ostream& os, const Register<T>& r) {
        return os << "Reg" << std::size(r.bits) << '(' << +r.value << ")"
            << "\nhex: 0x" << std::uppercase << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex
            << +r.bits.to_ullong() << std::dec << "\nbin: "
            << r.bits << "\n\n";
    }

    template<typename T>
    T changeEndian(T in) {
        char* const p = reinterpret_cast<char*>(&in);
        for (size_t i = 0; i < sizeof(T) / 2; ++i)
            std::swap(p[i], p[sizeof(T) - i - 1]);
        return in;
    }

    template<typename T>
    Register<T> reverseBitOrder(Register<T>& reg, bool copy = false) {
        static constexpr u16 BitCount = sizeof(T) * CHAR_BIT;

        auto str = reg.bits.to_string();
        std::reverse(str.begin(), str.end());

        if (copy) { // return a copy
            Register<T> cpy;
            cpy.bits = std::bitset<BitCount>(str);
            cpy.value = static_cast<T>(cpy.bits.to_ullong());
            return cpy;
        }
        else {
            reg.bits = std::bitset<BitCount>(str);
            //reg.previous_value = reg.value;
            reg.value = static_cast<T>(reg.bits.to_ullong());
            return {};
        }
    }    
} // namespace vpc

1 Ответ

3 голосов
/ 28 сентября 2019

Union не является заменой для вашего класса.У вас есть функции-члены, некоторые из которых читают bits member, в то время как другие читают value (может быть, некоторые читают оба, я не прошел через все это).

Только один из членов объединения может быть активнымв любой момент поведение чтения неактивного члена не определено (есть исключение, которое здесь не применяется).

Кроме того, анонимные структуры плохо сформированы в C ++.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...