在C ++中用union显式设置活动成员

时间:2017-11-15 15:28:25

标签: c++ type-conversion language-lawyer unions placement-new

以下代码是经典的,臭名昭着的类型 - 惩罚联盟。类似的代码在C99 / C11中是合法的,但在C ++中是非法的,因为必须从非活动成员中读取。

要激活非活动成员,必须写入(此处不合适)或通过placement-new构建它。

下面的代码就是这样:它初始化成员a,然后通过placement-new激活成员b。在原始类型数组的情况下,构造实际上是noop,并且bitvalues保持不变。

我从未在这个topix的众多其他问题/答案中看到这个解决方案,所以我想知道下面的代码是否真的绕过了UB。

#include <cstdint>
#include <cstddef>
#include <utility>
#include <array>
#include <tuple>
#include <cassert>

union ByteUnion32 {
public:
    explicit ByteUnion32(uint32_t v) : a{v}{
        new (&b) std::byte[sizeof(uint32_t)]; // set b active, but prevents constexpr, actually a noop
    }
    std::byte operator[](uint8_t i) const {
            return b[i];
    }
private:
    uint32_t a;
    std::byte b[sizeof(uint32_t)];
};
int main(){
    ByteUnion32 u10{0x01020304}; // initialise and activate byte-memebers
    auto v10 = std::to_integer<uint8_t>(u10[0]);    
    assert(v10 == 4);

}

2 个答案:

答案 0 :(得分:1)

来自[dcl.init]/12

  

如果没有为对象指定初始值设定项,则默认初始化该对象。当获得具有自动或动态存储持续时间的对象的存储时,该对象具有不确定值,并且如果没有对该对象执行初始化,则该对象保留不确定的值,直到该值被替换为止( [expr.ass])。 [...]如果评估产生了不确定的值,则行为是不确定的,除非在下列情况下:[...]

在此代码中:

assert(v10 == 4);

v10本身的初始化是未定义的行为,因为不适合任何项目符号(我不完全确定这一点),或者v10的初始化很好而且它只是被认为具有不确定的值,此时与4的最终比较是未定义的行为。

无论哪种方式,这都是未定义的行为。

答案 1 :(得分:0)

以下可能是C ++中使用两个特化的转换方向std::byte到更大的整数类型或整数类型到std::byte的解决方案,而不是使用联合提供转换方向。 / p>

#include <cstdint>
#include <cstddef>
#include <utility>
#include <array>
#include <tuple>
#include <cassert>
#include <cstring>

namespace std {
    enum class endian { // since c++20
#ifdef _WIN32
        little = 0,
        big    = 1,
        native = little
#else
        little = __ORDER_LITTLE_ENDIAN__,
        big    = __ORDER_BIG_ENDIAN__,
        native = __BYTE_ORDER__
#endif
    };
    namespace literals {
        constexpr byte operator"" _byte(unsigned long long v) { // <> Literal-Operator for suffix `_byte`
            return byte{static_cast<uint8_t>(v)};
        }
    }
}

template<typename T>
requires (std::is_same<uint64_t, T>::value || std::is_same<uint32_t, T>::value || std::is_same<uint16_t, T>::value) && (sizeof(T) > 1)
struct ByteUnion<ByteToIntegral, T> {
public:
    template<typename... B>
    requires (std::is_same<B, std::byte>::value || ...) && (sizeof...(B) == sizeof(T))
    explicit ByteUnion(B... vv) : ByteUnion{endian_type{}, std::index_sequence_for<B...>{}, vv...} {}

    const T& value() const {return a;}     
private:
    template<size_t... II, typename... B>
    explicit ByteUnion(LittleEndian, std::index_sequence<II...>, B&&... vv) {
        uint8_t* p = reinterpret_cast<uint8_t*>(&a);
        ((p[sizeof...(II) - 1 - II] = static_cast<uint8_t>(vv)),...);    
    }
    template<size_t... II, typename... B>
    explicit ByteUnion(BigEndian, std::index_sequence<II...>, B&&... vv) {
        uint8_t* p = reinterpret_cast<uint8_t*>(&a);
        ((p[II] = static_cast<uint8_t>(vv)),...);
    }
    T a;
};

using namespace std::literals;

int main(){
    ByteUnion<IntegralToByte, uint64_t> u1{0x01020304}; // initialise and activate byte-memebers
//    ByteUnion<ByteToIntegral, uint64_t> u1x{0x01020304}; // not possible
    auto v1 = std::to_integer<uint8_t>(u1[0]);    
//    u1.value(); // not possible, who access inactive member
    assert(v1 == 4);

    ByteUnion<ByteToIntegral, uint32_t> u2{0x01_byte, 0x02_byte, 0x03_byte, 0x04_byte}; // initialise and activate integral value member
//    ByteUnion<IntegralToByte, uint32_t> u2x{0x01_byte, 0x02_byte, 0x03_byte, 0x04_byte}; // not possible
    auto v2 = u2.value(); 
    assert(v2 == 0x01020304);
//    u2[0];

}