使用函数指针的模板化双重调度
templated double-dispatching using function pointers
我正在尝试为学术目的制作一个自定义碰撞引擎,但我陷入了一个一般的 c++ 编程问题。我已经拥有所有正常工作的几何形状,并且碰撞测试也正常工作。
引擎使用这两个类来创建几何队列来测试:
class collidable;
template<typename geometry_type>
class collidable_object : public collidable;
由于可以有多种几何类型,因此我不想手动指定要测试的任何碰撞。
相反,我使用了这个"技术"来实现双重调度:
class collidable
{
public:
typedef bool (collidable::*collidable_hit_function)(const collidable& ) const;
virtual ~collidable() = 0 {}
virtual collidable_hit_function get_hit_function() const = 0;
};
template<typename geometry_type>
class collidable_object : public collidable
{
public:
explicit collidable_object( geometry_type& geometry ) :
m_geometry( geometry )
{}
~collidable_object(){}
virtual collidable_hit_function get_hit_function() const
{
return static_cast<collidable_hit_function>( &collidable_object<geometry_type>::hit_function<geometry_type> );
}
template<typename rhs_geometry_type>
bool hit_function( const collidable& rhs ) const
{
return check_object_collision<geometry_type, rhs_geometry_type>( *this, rhs );
}
const geometry_type& geometry() const
{
return m_geometry;
}
private:
geometry_type& m_geometry;
};
bool check_collision( const collidable& lhs, const collidable& rhs )
{
collidable::collidable_hit_function hit_func = lhs.get_hit_function();
return (lhs.*hit_func)( rhs );
}
其中函数check_object_collision
是测试碰撞并已经过测试的模板函数。
我的问题如下:函数中的强制转换get_hit_function
确实编译但似乎可疑......我是否做了一些可怕的错误,这将导致未定义的行为和多个噩梦,或者是否可以将模板成员函数指针从一个派生类强制转换为另一个派生类。
让我感到困惑的是,在 Visual C++ 2012 中,它可以编译并且似乎可以正常工作......
是什么让这个演员阵容出了可怕的错误?
我真的不明白转换函数指针意味着什么......
作为一个后续问题,是否有办法以安全的方式实施这一点
指向方法的指针从基类强制转换为派生类。在相反的方向上,这是非常糟糕的主意。想想如果有人像这样使用你的代码会发生什么:
collidable_object<A> a;
collidable_hit_function f = a.get_hit_function();
collidable_object<B> b;
b.*f(...);
Yuor hit_function
(由f
指出(会期望this
被collidable_object<A>
,但相反它会得到collidable_object<B>
。如果这两个类足够相似,则不会出错,但您的代码可能已经在执行其他操作。如果你真的需要,你可以这样说,但你必须注意,你只在正确的类上使用此指针。
然而,更重要的是,你正在做的事情很可能在概念上是错误的。如果有两种几何类型A
和B
,并且检查是否与
collidable_object<A> a;
collidable_object<B> b;
check_collision(a,b);
那么你所做的最终是调用:
check_object_collision<A, A>();
所以你正在检查碰撞,就好像两个collidable
都是几何A
一样——我猜这不是你想做的。
这是您可能无法用任何单一语言结构解决的问题,因为它需要 2 维所有不同碰撞检查的数组,每对几何体一个,并且您需要类型擦除才能操作通用collidable
。
问:是否可以将模板成员函数指针从一个派生类强制转换为另一个派生类?
答:是的,如果 get_hit_function(( 确实兼容。
如果这是Java或C#,我只会声明一个接口:)
是的,标准明确允许从bool (collidable_object<geometry_type>::*)() const
到bool (collidable::*)() const
static_cast
,因为collidable
是一个可访问的、明确的非虚拟基类collidable_object<geometry_type>
。
将指向成员的指针转换为相反方向(从派生到基(时,不需要static_cast
。这是因为存在从bool (collidable::*)() const
到bool (collidable_object<geometry_type>::*)() const
的有效"标准转换"。
[会议记忆]
类型为"指向 cv T 类型的 B 成员的指针"的右值,其中 B 是类类型,可以转换为"指向 cv T 类型的 D 成员的指针"类型的右值,其中 D 是 B 的派生类。如果 B 是 D 的不可访问、不明确或虚拟基类,则需要此转换的程序格式不正确。转换的结果引用与转换发生之前指向成员的指针相同的成员,但它引用基类成员,就好像它是派生类的成员一样。结果引用了 D 实例中的 B 中的成员。
由于存在此有效的标准转换,并且collidable
是可访问的明确非虚拟基类collidable_object<geometry_type>
因此可以使用static_cast
从基转换为派生。
通过指向基成员的指针调用派生类[expr.static.cast]
"指向 cv1 T 类型的 D 成员的指针"类型的 rvalue,如果存在从"指向 T 类型为 B 的成员的指针"到"指向 T 类型为 D 的成员的指针"类型的 rvalue,则 B 是 D 的基类的有效标准转换, CV2 与 CV1 具有相同的 CV 资格,或比 CV1 更高的 CV 资格。[...]如果类 B 包含原始成员,或者是包含原始成员的类的基类或派生类,则指向成员的结果指针指向原始成员。否则,转换的结果是未定义的。[...]
成员函数时,必须确保用于调用它的基类对象是派生类的实例。否则,未定义的行为!
这是一个工作示例。取消注释 main 的最后一行会展示未定义的行为 - 不幸的是,这会破坏查看器。
编译器在幕后做了什么来使这项工作?这是一个完全不同的问题;)。
关于后续问题,实现双重调度的规范方法是使用 Visitor 模式。下面是如何将此应用于方案的工作示例:
#include <iostream>
struct Geom
{
virtual void accept(Geom& visitor) = 0;
virtual void visit(struct GeomA&) = 0;
virtual void visit(struct GeomB&) = 0;
};
struct GeomA : Geom
{
void accept(Geom& visitor)
{
visitor.visit(*this);
}
void visit(GeomA& a)
{
std::cout << "a -> a" << std::endl;
}
void visit(GeomB& b)
{
std::cout << "a -> b" << std::endl;
}
};
struct GeomB : Geom
{
void accept(Geom& visitor)
{
visitor.visit(*this);
}
void visit(GeomA& a)
{
std::cout << "b -> a" << std::endl;
}
void visit(GeomB& b)
{
std::cout << "b -> b" << std::endl;
}
};
void collide(Geom& l, Geom& r)
{
l.accept(r);
}
int main()
{
GeomA a;
GeomB b;
collide(a, a);
collide(a, b);
collide(b, a);
collide(b, b);
}
- "error: no matching function for call to"构造函数错误
- 什么时候调用组成单元对象的析构函数
- 继承函数的重载解析
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- C++模板来检查友元函数的存在
- 递归函数计算序列中的平方和(并输出过程)
- 对RValue对象调用的LValue ref限定成员函数
- 动态调度到模板函数C++
- C++ 如何按标签调度到不同的模板函数
- 是否会动态调度在具有 vtable 的类上调用非虚函数
- 带有模板化函数的C++调度表
- 使用 clock() 函数调度任务时出现问题
- 具有双重调度的C++多态循环函数
- 如果在没有限定的情况下从构造函数/析构函数调用虚拟函数,则是否会发生虚拟调度
- 我应该在高度动态的系统中使用优先级队列来调度任务(函数等)吗
- C++类方法,可以使用虚拟调度或类似于没有对象上下文的静态函数来调用
- 使用函数指针的模板化双重调度
- 基于非成员函数的参数调度
- 模板函数的动态调度
- 动态调度哪些函数