【问题标题】:Assigning std::function inside union crashes program在联合崩溃程序中分配 std::function
【发布时间】:2020-12-08 16:21:54
【问题描述】:

我编写了以下类,它存储固定值 (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 移出工会,则不会再发生崩溃。

您可以尝试一个现场示例here

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

【问题讨论】:

  • 具有构造函数的对象的联合总是很棘手。您必须显式构造此类对象。
  • fwiw,std::variant 是“新联盟”。
  • @Someprogrammerdude 显式构造是什么意思?
  • 他的意思是接收成员get没有正确构建。将行更改为auto get = value.get 将避免崩溃。
  • @Thomas 是的,但它实际上也不会实现任何目标

标签: c++ copy-constructor unions std-function


【解决方案1】:

为了change the active member of a union 到/从非平凡类型,您必须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,因为如果放置 new 失败,则无法恢复。如果你想避免这种情况,你需要为你的类添加一个异常无值状态,它可以被破坏,但没有其他操作是有效的。

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

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

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

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

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

Example.

【讨论】:

  • 如果 T 是具有非平凡 ctor/dtor 的类型,这仍然会失败。最好只使用std::variant,它会为您完成所有工作。
  • @ChrisDodd 好点,已更新。我认为值得知道如何实现一个简单的标记联合,即使只是知道选择使用标准变体的权衡是什么。
  • @ChrisDodd 更新以处理非平凡的T。这不是过多的工作,特别是如果您准备在强大的异常安全保证等方面做出妥协。
【解决方案2】:

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

【讨论】:

  • 看起来很草率。如果valueisConstant == true 怎么办?然后get 将被不优雅地覆盖。
  • 而且,事实上,做这样的事情会有未定义的行为。
  • 在 C++ 中正确处理非 POD 内容确实很棘手,这正是我们使用 std::variant 代替的原因。
猜你喜欢
  • 1970-01-01
  • 2011-01-19
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
  • 1970-01-01
  • 2012-03-06
  • 1970-01-01
  • 2016-12-18
相关资源
最近更新 更多