为什么我的变体 return 的值不等于分配给它的值?

Why does my variant return a value not equal to what it was assigned?

我正在尝试创建一个简单的变体作为学习练习。

我想在不动态分配内存的情况下执行此操作,正如 std::variant 的 c++ 规范所指定的那样。

为简化起见,我的变体只能取两个值。

这是我的实现:

//variant style class for two types
template<typename T1, typename T2>
class Either {
    using Bigest = std::conditional<sizeof(T1) >= sizeof(T2), T1, T2>;
    using ByteArray = std::array<std::byte, sizeof(Bigest)>;

    ByteArray val;
    std::optional<std::type_index> containedType;

public:

    Either() : containedType(std::nullopt) {} 

    template<typename T>
    Either(const T& actualVal) : containedType(typeid(T)) {         //ToDo check T is one of correct types
        ByteArray* ptr = (ByteArray*)&actualVal;
        val = *ptr;
    }

    class BadVariantAccess {};

    template<typename T>
    inline T& getAs() const {
        if(containedType == typeid(T)) {
            T* ptr = (T*)val.data();
            return *ptr;
        }
        else throw BadVariantAccess();
    }
};

但是,当我测试这个时,我在尝试获取值后得到了一个错误的数字:

int main() {

    Either<int,float> e = 5;

    std::cout << e.getAs<int>() << std::endl;

    return 0;
}

Returns 一个随机数(例如 272469509)。

我的实施有什么问题,我该如何解决?

    ByteArray* ptr = (ByteArray*)&actualVal;

这是荒谬的。您是说 ptr 指向 std::array,但事实并非如此。当然,如果你取消引用它,你会得到垃圾。

您的程序由于各种原因表现出未定义的行为,因此试图解释为什么您会观察到给定的行为有点毫无意义。但是您的代码存在一些严重问题:

  1. 将任何内存别名为 std::array<std::byte, N> 是不安全的。这违反了严格别名规则,唯一的例外是指向 charstd::byte 的指针。在执行 ByteArray* ptr = (ByteArray*)&actualVal; val = *ptr; 时,您正在调用 std::array's 复制构造函数并传递一个不存在的实例。在这一点上,几乎任何事情都可能发生。您应该改为逐个复制字节(对于普通类型),或者使用放置 new 在基于字节的存储中复制构造对象。

  2. 您的存储未对齐,这可能会导致运行时崩溃或严重的性能损失,具体取决于您的目标平台。我不确定这是否会导致未定义的行为,但如果您想继续这种低级内存管理,您一定要解决这个问题。

  3. 您的复制构造函数不检查分配类型在内存中的实际大小。如果在某些平台上,您有 sizeof(int) > sizeof(float),那么当您从 float 复制构造一个 Either<int, float> 时,您将读取超过浮点数末尾的字节,并且很容易导致未定义的行为。您必须考虑分配的类型的大小

  4. 如果您打算存储除琐碎类型以外的任何内容(即 std::stringstd::vector,而不仅仅是基元),您需要调用适当的 copy/move constructor/assignment 运算符。对于构造函数(默认、移动、复制),您需要使用放置 new 在预分配存储中构造活动对象。此外,您需要使用类型擦除来存储一些函数,这些函数会将包含的对象破坏为正确的类型。 std::function<void(std::byte*)> 中的 lambda 在这里可能非常有用,它只调用析构函数: [](std::byte* data){ (*reinterpret_cast<T*>(data)).~T(); } 每当您存储新类型时都必须分配它。请注意,这种情况几乎是您唯一一次想要手动调用析构函数的情况。

我强烈建议您仔细阅读有关如何正确执行此类低级内存管理的内容。很容易做错并且一无所知,直到后来你被奇怪的错误所困扰。


正如@MilesBudnek 所指出的那样,using Biggest = std::conditional<sizeof(T1) >= sizeof(T2), T1, T2> 将给出类型 trait,因此 Biggest 的实例实际上是一个实例结构 std::conditionalnot T1T2 的特化。您的意思可能是 std::conditional_t<...>std::conditional<...>::type; 这可能会导致您的 Either class 只会分配一个字节,这显然是不正确的。