在 union 内部分配 std::function 会使程序崩溃

Assigning std::function inside union crashes program

我写了下面的 class 它存储一个固定值 (constant) 或一个获取值的函数 (get)。

template <typename T>
class DynamicValue {
    private:
    bool isConstant;
    union {
        T constant;
        std::function<T()> get;
    };
    void copy(const DynamicValue& value) {
        isConstant = value.isConstant;
        if (isConstant) {
            constant = value.constant;
        } else {
            std::cout << "won't overcome next line" << std::endl;
            get = value.get;
            std::cout << "why!" << std::endl;
        }
    }

    public:
    DynamicValue(const T& constant) : isConstant(true), constant(constant){};
    template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
    DynamicValue(F&& get) : isConstant(false), get(std::forward<F>(get)) {}
    DynamicValue(const T* pointer) : DynamicValue([pointer]() { return *pointer; }) {}
    DynamicValue(const DynamicValue& value) { copy(value); }
    ~DynamicValue() {}
    DynamicValue& operator=(const DynamicValue& value) {
        copy(value);
        return *this;
    }
    operator T() { return isConstant ? constant : get(); }
};

我还写了以下虚拟 class 来展示我遇到的问题:

class Object {
    private:
    DynamicValue<int> num;
    
    public:
    Object(DynamicValue<int> num) : num(num) {}
};

问题是当我尝试用函数初始化 Object 时(例如通过执行 Object b([] { return 1; });)程序崩溃。我已将崩溃范围缩小到复制函数内的 get = value.get; 行。我还注意到,如果我将 get 移到 union 之外,崩溃将不再发生。

您可以尝试一个实例 here

为什么会发生这种情况,我该如何解决(将 get 保留在联合内)?

DynamicValue 的复制构造函数没有初始化 get 成员,导致 copy 中的赋值失败。为 get

添加初始化
DynamicValue(const DynamicValue& value) : get() { copy(value); }

解决了崩溃问题,但还有其他问题。查看版本 here

朝这个方向前进是有问题的,所以你应该考虑改变你的方法来使用 std::variant

template <typename T>
class DynamicValue {
    private:
    std::variant<T, std::function<T()>> value;
    void copy(const DynamicValue& other) {
        value = other.value;
    }

    public:
    DynamicValue(const T& constant) : value(constant){};
    template <typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
    DynamicValue(F&& get) : value(std::forward<F>(get)) {}
    DynamicValue(const T* pointer) : DynamicValue([pointer]() { return *pointer; }) {}
    DynamicValue(const DynamicValue& value) { copy(value); }
    ~DynamicValue() {}
    DynamicValue& operator=(const DynamicValue& value) {
        copy(value);
        return *this;
    }
    operator T() { return value.index() == 0 ? std::get<0>(value) : std::get<1>(value)(); }
};

查看工作版本here

为了 to/from a non-trivial type, you must placement new construct it / call its destructor:

void copy(const DynamicValue& value) noexcept {
    if (isConstant && value.isConstant) {
        constant = value.constant;
    } else if (!isConstant && !value.isConstant) {
        get = value.get;
    } else if (value.isConstant) {
        get.~function();
        new (&constant) T(value.constant);
    } else {
        constant.~T();
        new (&get) std::function<T()>(value.get);
    }
    isConstant = value.isConstant;
}

注意这里的copynoexcept,因为如果placement new失败就没有办法恢复了。如果你想避免这种情况,你需要在你的 class 中添加一个无价值的异常状态,它可以被破坏,但没有其他操作有效。

此外,由于copy的前提条件是您的对象处于有效状态,因此您的复制构造函数必须首先将自身初始化为constant状态:

DynamicValue(const DynamicValue& value) : DynamicValue(T{}) { copy(value); }

请注意,这假设 T 是默认可构造的。或者,您可以初始化为 get 状态,但这可能效率较低,特别是如果 T 是微不足道的。

最后,为了防止泄漏,您应该确保析构函数将联合置于微不足道的状态:

~DynamicValue() {
    if (isConstant)
        constant.~T();
    else
        get.~function();
}

Example.