0

我正在尝试为学术目的制作一个自定义碰撞引擎,但我陷入了一个一般的 c++ 编程问题。我已经拥有所有可以正常工作的几何图形,并且碰撞测试也可以正常工作。

引擎使用这 2 个类来创建要测试的几何队列:

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 中,它可以编译并且似乎可以正常工作......

是什么让这个演员出现了可怕的错误?

我真的不明白强制转换函数指针意味着什么......

作为后续问题,是否有办法以安全的方式实现这一点

4

3 回答 3

1

可以将指向方法的指针从基类转换为派生类。在相反的方向这是非常糟糕的主意。想想如果有人像这样使用你的代码会发生什么:

collidable_object<A> a;
collidable_hit_function f = a.get_hit_function();

collidable_object<B> b;
b.*f(...);

Yuor hit_function(由 指向f)会期望thiscollidable_object<A>,但会得到collidable_object<B>。如果这两个类足够相似,您将不会出错,但您的代码可能已经在做一些其他事情。如果你真的需要的话,你可以这样设置它,但是你必须注意你只在正确的类上使用这个指针。

然而,更重要的是,您所做的很可能在概念上是错误的。如果您有两种几何类型AB,并且您检查与

collidable_object<A> a;
collidable_object<B> b;
check_collision(a,b);

那么你所做的最终是调用:

check_object_collision<A, A>();

所以你正在检查碰撞,就好像两个collidables 都是几何图形一样A- 我猜这不是你想要做的。

这是您可能无法使用任何单一语言构造解决的问题,因为它需要不同碰撞检查的二维数组,每对几何图形一个,并且您需要类型擦除才能操作泛型collidables。

于 2013-07-05T01:39:28.713 回答
0

问:可以将模板成员函数指针从一个派生类转换到另一个派生类吗?

答:是的,如果 get_hit_function() 真正兼容的话。

如果这是 Java 或 C#,我只需声明一个接口 :)

于 2013-07-05T00:05:47.853 回答
0

是的,标准明确允许static_castfrom bool (collidable_object<geometry_type>::*)() constto bool (collidable::*)() const,因为collidable它是一个可访问的明确非虚拟基类collidable_object<geometry_type>.

当以相反方向转换指向成员的指针时 - 从派生到基 -static_cast不需要。这是因为存在从bool (collidable::*)() const到的有效“标准转换” bool (collidable_object<geometry_type>::*)() const

[conv.mem]

“指向类型为 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 成员的指针”类型的右值可以转换为“指向 cv2 T 的 B 成员的指针”类型的右值,其中 B 是 D 的基类,如果从“指向类型 T 的 B 成员的指针”到“指向类型 T 的 D 成员的指针”存在,且 cv2 与 cv1 具有相同的 cv 限定或大于 cv1 的 cv 限定。[...] 如果类 B 包含原始成员,或者是包含原始成员的类的基类或派生类,则指向成员的结果指针指向原始成员。否则,强制转换的结果是未定义的。[...]

当您通过指向基成员的指针调用派生类成员函数时,您必须确保调用它的基类对象是派生类的实例。否则,未定义的行为!

这是一个工作示例。取消注释 main 的最后一行演示了未定义的行为 - 不幸的是,这会破坏查看器。

编译器在幕后做了什么来完成这项工作?这是一个完全不同的问题;)。

关于后续问题,实现双重调度的规范方法是使用访问者模式。这是一个如何将其应用于您的场景的工作示例:

#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);
}
于 2013-07-06T15:03:17.463 回答