在 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;
}
注意这里的copy
是noexcept
,因为如果placement new失败就没有办法恢复了。如果你想避免这种情况,你需要在你的 class 中添加一个无价值的异常状态,它可以被破坏,但没有其他操作有效。
此外,由于copy
的前提条件是您的对象处于有效状态,因此您的复制构造函数必须首先将自身初始化为constant
状态:
DynamicValue(const DynamicValue& value) : DynamicValue(T{}) { copy(value); }
请注意,这假设 T
是默认可构造的。或者,您可以初始化为 get
状态,但这可能效率较低,特别是如果 T
是微不足道的。
最后,为了防止泄漏,您应该确保析构函数将联合置于微不足道的状态:
~DynamicValue() {
if (isConstant)
constant.~T();
else
get.~function();
}
我写了下面的 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
为了
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;
}
注意这里的copy
是noexcept
,因为如果placement new失败就没有办法恢复了。如果你想避免这种情况,你需要在你的 class 中添加一个无价值的异常状态,它可以被破坏,但没有其他操作有效。
此外,由于copy
的前提条件是您的对象处于有效状态,因此您的复制构造函数必须首先将自身初始化为constant
状态:
DynamicValue(const DynamicValue& value) : DynamicValue(T{}) { copy(value); }
请注意,这假设 T
是默认可构造的。或者,您可以初始化为 get
状态,但这可能效率较低,特别是如果 T
是微不足道的。
最后,为了防止泄漏,您应该确保析构函数将联合置于微不足道的状态:
~DynamicValue() {
if (isConstant)
constant.~T();
else
get.~function();
}