boost te
库(MSVC 19.29.30153.0 与 c++ 标准 20)。我举个最简单的例子擦除它,本质上是:
struct Drawable {
void draw(std::ostream &out) const {
te::call([](auto const &self, auto &out) { self.draw(out); }, *this, out);
}
};
struct Square {
void draw(std::ostream &out) const { out << "Square"; }
};
int main() {
te::poly<Drawable> d = Square{};
d.draw();
}
这可以编译,但在运行时失败,此时在
te::poly
:
return reinterpret_cast<R (*)(void *, Ts...)>(self.vptr[N - 1])(
self.ptr(), std::forward<Ts>(args)...);
发生读访问冲突:
抛出异常:读取访问冲突。 self 是 0xFFFFFFFFFFFFFFFF。
如果我添加一行来观察变量:
auto my_pointer = self.ptr();
它确实无法调用它,并且在检查变量时:
self {vptr=0x00007ff6698614e2 {check_te_windows.exe! ...
+ __vfptr 0x00000142775a64a0 {0x000000fdfdfdfdcd} void * *
+ vptr 0x00007ff6698614e2
vtable 指针似乎确实损坏了。这个库已经在 clang、gcc 和 intels oneapi 编译器中为我工作了。这是一个 gcc 实例:https://godbolt.org/z/boKaY19oj
有人知道 Visual C++ 编译器可能出现什么问题吗?
在类模板中
poly
构造函数初始值设定项列表显示vtable
的初始值设定项在storage
之前,但storage
仍然首先初始化,因为声明顺序:
constexpr explicit poly(T &&t, std::index_sequence<Ns...>) noexcept(std::is_nothrow_constructible_v<T_,T&&>)
: detail::poly_base{},
vtable{std::forward<T>(t), vptr,
std::integral_constant<std::size_t, sizeof...(Ns)>{}},
storage{std::forward<T>(t)} {
static_assert(sizeof...(Ns) > 0);
static_assert(std::is_destructible_v<T_>, "type must be desctructible");
static_assert(std::is_copy_constructible_v<T_> ||
std::is_move_constructible_v<T_>,
"type must be either copyable or moveable");
(init<Ns + 1, std::decay_t<T> >(
decltype(get(detail::mappings<I, Ns + 1>{})){}),
...);
}
这是可疑的,但更可疑的是双重
std::forward
,它可能会导致t
论证的双重移动。在您的示例中,确实使用以下模板参数调用了这个可疑的构造函数:
constexpr boost::ext::te::v1::poly<I, TStorage, TVtable>::poly(T&&, std::index_sequence<Ns ...>);
// with T = Square;
// T_ = Square
// long unsigned int ...Ns = {0}
// I = Drawable
// TStorage = boost::ext::te::v1::dynamic_storage
// TVtable = boost::ext::te::v1::static_vtable
// std::index_sequence<Ns ...> = std::integer_sequence<long unsigned int, 0>
检测 Square 类以禁止移动,使其无法编译:https://godbolt.org/z/v4bjYf18b
基于这些观察,您可能会考虑避免右值参数(您的临时参数):https://godbolt.org/z/cMhd6Pa4h(当然,在 GCC 上没有问题,但检查 MSVC?)
int main() {
Circle c;
Square s;
draw(c); // prints Circle
draw(s); // prints Square
}
您可以翻转
poly
成员的声明顺序:
TStorage storage;
TVtable vtable;
至:
TVtable vtable;
TStorage storage;
当然,这会改变布局,并且根据邪恶的做法可能会导致其他事物改变行为。
无论如何,我建议向 TE 开发人员提出问题。
还可以考虑使用 Boost Type Erasure,它是 Boost 的正确组成部分:https://www.boost.org/doc/libs/1_84_0/doc/html/boost_typeerasure.html