重载基类非虚函数中使用的虚函数

overloading a virtual function that is used in a base classes non-virtual function

本文关键字:函数 基类 重载      更新时间:2023-10-16

嘿,所以我试图建立类的以下成员函子ConcavePolygon,我得到链接器外部符号错误出于某种原因:

未解析的外部符号"public: virtual void __thiscall "sf::ConcavePolygon::Partition::RunAlgorithm(class TPPLPoly &Std::list> &)"

我的目标是简单地制作一个类来保存SFML (lib)可以理解的图形数据,以及对图形数据(多边形)进行分区或三角测量的函数

与其写两个代码非常相似的大函数;一个用于三角化,一个用于凹凸化,我决定尝试使用函子,并将基函子划分为派生三角化和凹凸化

基类Partition只包含两个函数:

  • 包含所有功能的构造函数
  • RunAlgorithm函数(从构造函数中分离出来,所以它可以被后代重载)

我认为这个错误与虚拟RunAlgorithm函数有关,因为它是由构造函数依赖的,但我猜它是由后代以某种方式呈现无效的。

我该怎么做才能实现我的目标或解决这个问题?

代码如下:

class ConcavePolygon : public Body{ 
protected:
    std::list<Vector2f> SFMLPoints;
    std::vector <TPPLPoint> TPPLPoints; //TODO: figure out how to make a temp version without Memory Exception
public:
//////////////////// Partitioning/Triangulating Classes /////////////////////////////////////////////////////////////
    class Partition{
    public:
        virtual void RunAlgorithm(TPPLPoly& Poly, std::list<TPPLPoly>& PartitionOutput);
        Partition(){};
        Partition(ConcavePolygon* Poly, Vector2f* Points, long numbPoints){ //TODO turn this into a base class for triangulate or Convexulate
        //rev up all the needed data structs
        std::list<TPPLPoly> PartitionOutput;
        std::list <TPPLPoly> ::iterator I;
        //Backup the points, and convert them to tppl
        for(int I=0; I<numbPoints; I++){
            Poly->TPPLPoints.push_back(TPPLPoint(Points[I].x, Points[I].y));
            Poly->SFMLPoints.push_back(Points[I]);}
        TPPLPoly TempPoly(&Poly->TPPLPoints[0], numbPoints, false);
        //clear everything to be filled with the Partition Algorithm
        Poly->Clear();
        // Run the Partitioning Algorithm (This is an abstract function, and is overloaded)
        RunAlgorithm(TempPoly, PartitionOutput);
        // Convert results to SFML points, shapes, and add to the body
        for( I= PartitionOutput.begin(); I!= PartitionOutput.end();I++){
            sf::Shape TempShape;
            for(int i=0; i< I->GetNumPoints(); i++)
                TempShape.AddPoint( I->GetPoint(i).x, I->GetPoint(i).y);
            Poly->AddShape(TempShape);
        }
    };
};
    class Convexulate: public Partition{
    public:
        Convexulate(ConcavePolygon* Poly, Vector2f* Points, long numbPoints){
            Partition(Poly, Points, numbPoints);};
        void RunAlgorithm(TPPLPoly& Poly, std::list<TPPLPoly>& PartitionOutput){
            TPPLPartition Partition;
            Partition.ConvexPartition_OPT(&Poly, &PartitionOutput);
        };
    };
    class Triangulate: public Partition{
    public:
        Triangulate(ConcavePolygon* Poly, Vector2f* Points, long numbPoints){
            Partition(Poly, Points, numbPoints);};
        void RunAlgorithm(TPPLPoly& Poly, std::list<TPPLPoly>& PartitionOutput){
            TPPLPartition Partition;
            Partition.Triangulate_OPT(&Poly, &PartitionOutput);
        };
    };
//////////////////////  Constructors    /////////////////////////////////////////////////////
    ConcavePolygon(Vector2f* Points, long numbPoints){
        Convexulate(this,Points, numbPoints);
    };

};// ConcavePolygon Class

看起来在您的Partition子类中,您没有正确地将调用转发给Partition构造函数。相反,您正在构造一个临时的Partition对象,然后立即将其丢弃。

struct Base {
    Base() { cout << "Base Default" << endl; }
    Base(int i) { cout << "Base Int: " << i << endl;
    ~Base() { cout << "~Base" << endl;
}
struct DerivedWrong : Base {
    DerivedWrong(int i) {
        Base(i);
        cout << "Derived Int: " << i << endl;
    }
};

如果你构造一个DerivedWrong对象,输出应该是

Base Default
Base Int: 5
~Base
Derived Int: 5

看到派生类构造函数输出前的那个鬼鬼祟祟的析构函数调用了吗?这是被破坏的临时对象。在那个临时对象的构造函数中,它试图调用RunAlgorithm,而您没有实现它,从而导致链接器错误。如果你让RunAlgorithm纯虚[你应该这样做,顺便说一下]你会得到一个关于构造抽象类型的错误,而不是链接器错误,这可能对你更有用。下面的代码理论上可以解决这个问题:

struct DerivedRight : Base {
    DerivedRight(int i)
      : Base(i)
    { cout << "Derived Int: " << i << endl; }
};

在本例中,输出是您所期望的。

Base Int: 5
Derived Int: 5

然而,仍然有一个问题:你不能从基类构造函数调用虚函数并获得多态行为…最终调用函数的基类版本,无论它是否被重写。您必须等到对象完全构造好之后才能获得多态行为。

基本上,在构造的每一步,对象是正在构造的。尽管它最终将是一个Triangulate对象,但在Partition的构造函数中,它的行为与Partition一样。这意味着你仍然会以链接器错误结束。

请注意,您仍然可以从构造函数内部调用虚方法,您只需要非常了解这种行为。如果您非常确定Triangulate等永远不会被派生,您可以从基类构造函数中提取代码,并将其放入从派生类的构造函数中调用的Init方法中。在该方法中,虚拟调度将按所需方式运行。

你也可以考虑延迟初始化…存储输入参数,并且只在第一次执行函子时执行计算。之后,只需返回缓存的结果。这需要相当多的额外开销,但具有完全安全的好处,无论最终的继承图是什么样子。但是,有状态函数也有自己的问题,这取决于您计划如何使用该类。

class Partition内部有virtual void RunAlgorithm()的方法体,或者将其声明为纯virtual:

virtual void RunAlgorithm(TPPLPoly& Poly, std::list<TPPLPoly>& PartitionOutput) = 0;

有一个特殊的情况,任何虚函数都不能在该类或其子对象声明时保持未实现状态(即使函数本身没有被使用)。