首页 > 解决方案 > 在联合崩溃程序中分配 std::function

问题描述

我编写了以下类,它存储一个固定值(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 Object {
    private:
    DynamicValue<int> num;
    
    public:
    Object(DynamicValue<int> num) : num(num) {}
};

问题是当我尝试Object用一​​个函数初始化一个(Object b([] { return 1; });例如)时,程序崩溃了。我已将崩溃范围缩小到get = value.get;复制功能内的行。我还注意到,如果我离开get工会,崩溃就不会再发生了。

你可以在这里尝试一个活生生的例子。

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

标签: c++copy-constructorunionsstd-function

解决方案


为了将联合的活动成员更改为非平凡类型/从非平凡类型更改,您必须放置新的构造它/调用它的析构函数

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因为如果放置 new 失败,则无法恢复。如果你想避免这种情况,你需要在你的类中添加一个 valueless-by-exception 状态,它可以被破坏,但没有其他操作是有效的。

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

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

请注意,这假设它T是默认可构造的。或者,您可以初始化到get状态,但这可能效率较低,尤其T是在微不足道的情况下。

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

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

例子


推荐阅读