Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/124.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如果在C+;中只有一个指向基类的指针,请使用派生类参数重载函数+;_C++_Polymorphism_Derived Class - Fatal编程技术网

C++ 如果在C+;中只有一个指向基类的指针,请使用派生类参数重载函数+;

C++ 如果在C+;中只有一个指向基类的指针,请使用派生类参数重载函数+;,c++,polymorphism,derived-class,C++,Polymorphism,Derived Class,我见过有人使用指向基类的指针容器来保存共享相同虚拟函数的对象组。是否可以将派生类的重载函数与这些基类指针一起使用。很难解释我的意思,但(我认为)很容易用代码显示 class PhysicsObject // A pure virtual class { // Members of physics object // ... }; class Circle : public PhysicsObject { // Members of circle // ... }

我见过有人使用指向基类的指针容器来保存共享相同虚拟函数的对象组。是否可以将派生类的重载函数与这些基类指针一起使用。很难解释我的意思,但(我认为)很容易用代码显示

class PhysicsObject // A pure virtual class
{
    // Members of physics object
    // ...
};

class Circle : public PhysicsObject
{
    // Members of circle
    // ...
};

class Box : public PhysicsObject
{
    // Members of box
    // ...
};

// Overloaded functions (Defined elsewhere)
void ResolveCollision(Circle& a, Box& b);
void ResolveCollision(Circle& a, Circle& b);
void ResolveCollision(Box& a, Box& b);

int main()
{
    // Container to hold all our objects 
    std::vector<PhysicsObject*> objects;

    // Create some circles and boxes and add to objects container
    // ...

    // Resolve any collisions between colliding objects
    for (auto& objA : objects)
        for (auto& objB : objects)
            if (objA != objB)
                ResolveCollision(*objA, *objB); // !!! Error !!! Can't resolve overloaded function
}

通过谷歌搜索这个问题,似乎可以用铸造来解决,但我不知道如何找到正确的铸造类型(也很难看)。我怀疑我问了错误的问题,有一种更好的方法来构造我的代码,它避开了这个问题并获得了相同的结果。

使用双重分派,它将类似于:

class Circle;
class Box;

// Overloaded functions (Defined elsewhere)
void ResolveCollision(Circle& a, Box& b);
void ResolveCollision(Circle& a, Circle& b);
void ResolveCollision(Box& a, Box& b);
class PhysicsObject // A pure virtual class
{
public:
    virtual ~PhysicsObject() = default;

    virtual void ResolveCollision(PhysicsObject&) = 0;
    virtual void ResolveBoxCollision(Box&) = 0;
    virtual void ResolveCircleCollision(Circle&) = 0;
};

class Circle : public PhysicsObject
{
public:
    void ResolveCollision(PhysicsObject& other) override { return other.ResolveCircleCollision(*this); }
    void ResolveBoxCollision(Box& box) override { ::ResolveCollision(*this, box);}
    void ResolveCircleCollision(Circle& circle) override { ::ResolveCollision(*this, circle);}
    // ...
};

class Box : public PhysicsObject
{
public:
    void ResolveCollision(PhysicsObject& other) override { return other.ResolveBoxCollision(*this); }
    void ResolveBoxCollision(Box& box) override { ::ResolveCollision(box, *this);}
    void ResolveCircleCollision(Circle& circle) override { ::ResolveCollision(circle, *this);}
    // ...
};

我这样做的方法是构建一个
Extent
类,它告诉你一个对象的物理周长,也许是关于它的重心。此外,您还需要

virtualconst-Extent&getExtent()const=0

PhysicsObject
类中。然后每个对象类型实现一次
getExtent

您的碰撞检测线变为

ResolveCollision(objA->getExtent(), objB->getExtent());
尽管从某种意义上说,随着复杂性被推到
范围
类,这种方法只需在一个对象上构建一个方法,因此可以很好地扩展


另一种双重分派机制是侵入性的,因为新形状需要对所有现有形状进行调整。例如,如果您引入一个
椭圆
类,那么必须重新编译
类对我来说是一种代码味道。

我将绘制一个不依赖双重分派的实现。相反,它使用一个所有函数都注册的表。然后使用对象的动态类型(作为基类传递)访问该表

首先,我们有一些示例形状。它们的类型登记在
枚举类中。每个形状类都定义了一个
MY_TYPE
作为各自的枚举条目。此外,它们必须实现基类的纯虚拟
类型
方法:

enum class ObjectType
{
    Circle,
    Box,
    _Count,
};

class PhysicsObject
{
public:
    virtual ObjectType type() const = 0;
};

class Circle : public PhysicsObject
{
public:
    static const ObjectType MY_TYPE = ObjectType::Circle;

    ObjectType type() const override { return MY_TYPE; }
};

class Box : public PhysicsObject
{
public:
    static const ObjectType MY_TYPE = ObjectType::Box;

    ObjectType type() const override { return MY_TYPE; }
};
接下来,你有你的冲突解决功能,它们必须根据形状来实现,当然

void ResolveCircleCircle(Circle* c1, Circle* c2)
{
    std::cout << "Circle-Circle" << std::endl;
}

void ResolveCircleBox(Circle* c, Box* b)
{
    std::cout << "Circle-Box" << std::endl;
}

void ResolveBoxBox(Box* b1, Box* b2)
{
    std::cout << "Box-Box" << std::endl;
}
表本身是一个2d数组,包含
std::function
s。请注意,这些函数接受指向
PhysicsObject
的指针,而不是派生类。然后,我们使用一些宏来方便注册。当然,相应的代码可以手工编写,我很清楚使用宏通常被认为是一种坏习惯。然而,在我看来,这些类型的东西正是宏所擅长的,只要您使用有意义的名称,并且不会使全局名称空间混乱,它们是可以接受的。再次注意,只注册了
圆圈
-
,而不是相反

现在来看看奇妙的宏:

#define CONCAT2(x,y) x##y
#define CONCAT(x,y) CONCAT2(x,y)

#define REGISTER_RESOLVE_FUNCTION(o1,o2,fn) \
    const bool CONCAT(__reg_, __LINE__) = []() { \
        int o1type = static_cast<int>(o1::MY_TYPE); \
        int o2type = static_cast<int>(o2::MY_TYPE); \
        assert(o1type <= o2type); \
        assert(!ResolveFunctionTable[o1type][o2type]); \
        ResolveFunctionTable[o1type][o2type] = \
            [](PhysicsObject* p1, PhysicsObject* p2) { \
                    (*fn)(static_cast<o1*>(p1), static_cast<o2*>(p2)); \
            }; \
        return true; \
    }();
它接受参数的动态类型,并将它们传递给在
ResolveFunctionTable
中为这些相应类型注册的函数。请注意,如果参数不按顺序排列,则会进行交换。因此,您可以使用
Box
Circle
自由调用
resolvelision
,然后它将在内部调用为
Circle
-
Box
冲突注册的函数

最后,我将举例说明如何使用它:

int main(int argc, char* argv[])
{
    Box box;
    Circle circle;

    ResolveCollision(&box, &box);
    ResolveCollision(&box, &circle);
    ResolveCollision(&circle, &box);
    ResolveCollision(&circle, &circle);
}
很简单,不是吗?有关上述功能的工作实现,请参阅


现在,这种方法的优势是什么?以上代码基本上就是支持任意数量形状所需的全部代码。假设您要添加一个
三角形
。你所要做的就是:

  • ObjectType
    enum中添加一个条目
    Triangle
  • 实现
    ResolveTriangalExxx
    函数,但在任何情况下都必须这样做
  • 使用宏:
    Register_RESOLVE_函数(三角形、三角形和ResolveTriangle)将它们注册到表中
  • 就这样。无需向
    PhysicsObject
    添加更多方法,无需在所有现有类型中实现方法


    我知道这种方法有一些“缺陷”,比如使用宏、拥有所有类型的中央
    enum
    、依赖全局表。如果将形状类构建到多个共享库中,则后一种情况可能会导致一些问题。然而,在我看来,这种方法非常实用(除了非常特殊的用例),因为它不会像其他方法(例如双重分派)那样导致代码爆炸

    请看。问题很明显-没有重载将2
    PhysicsObject
    s作为参数,因此出现错误。要么提供重载,要么使用
    dynamic\u cast
    。当然,我可以创建一个重载,它包含两个物理对象,但我仍然不知道它们是哪种类型的物理对象,所以我有相同的问题。谢谢@Jarod42,我现在就来读一读。你的代码清楚地显示了这一点,但只需提一下:明显的缺点是,对于
    n
    涉及的类,每个类都需要
    n
    方法。谢谢,我以前没有听说过双重分派,但使用你的答案很容易适应我自己的代码。如果你没有太多的形状,这似乎是一个简单的解决方案。我也做了,这可能会简化使用(即使完整的实现更复杂)。你为此付出了很多努力-尽管不是我会这样做(我的方法更简单,IMHO),这显然值得一试:这是一个合理的解决方案。谢谢你抽出时间。非常感谢,非常感谢@nh_uuu-谢谢,我已经预见到了一个问题,即当添加更多的形状时,您的解决方案无法很好地扩展。很难选择答案,因为它们都有不同的帮助方式。我认为你的答案可能是最好的
    #define CONCAT2(x,y) x##y
    #define CONCAT(x,y) CONCAT2(x,y)
    
    #define REGISTER_RESOLVE_FUNCTION(o1,o2,fn) \
        const bool CONCAT(__reg_, __LINE__) = []() { \
            int o1type = static_cast<int>(o1::MY_TYPE); \
            int o2type = static_cast<int>(o2::MY_TYPE); \
            assert(o1type <= o2type); \
            assert(!ResolveFunctionTable[o1type][o2type]); \
            ResolveFunctionTable[o1type][o2type] = \
                [](PhysicsObject* p1, PhysicsObject* p2) { \
                        (*fn)(static_cast<o1*>(p1), static_cast<o2*>(p2)); \
                }; \
            return true; \
        }();
    
    void ResolveCollision(PhysicsObject* p1, PhysicsObject* p2)
    {
        int p1type = static_cast<int>(p1->type());
        int p2type = static_cast<int>(p2->type());
        if(p1type > p2type) {
            std::swap(p1type, p2type);
            std::swap(p1, p2);
        }
        assert(ResolveFunctionTable[p1type][p2type]);
        ResolveFunctionTable[p1type][p2type](p1, p2);
    }
    
    int main(int argc, char* argv[])
    {
        Box box;
        Circle circle;
    
        ResolveCollision(&box, &box);
        ResolveCollision(&box, &circle);
        ResolveCollision(&circle, &box);
        ResolveCollision(&circle, &circle);
    }