【问题标题】:overloading a virtual function that is used in a base classes non-virtual function重载在基类非虚函数中使用的虚函数
【发布时间】:2011-07-16 03:34:38
【问题描述】:

嘿,所以我正在尝试构建 ConcavePolygon 类的以下成员仿函数,但由于某种原因,我遇到了链接器外部符号错误:

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

我的目标是简单地创建一个包含 SFML (lib) 可以理解的图形数据的类,以及对图形数据进行分区或三角剖分的功能(多边形)

而不是用非常相似的代码编写两个大函数;一个用于三角化,一个用于做我所说的凸形,我决定尝试使用函子,并制作了基本函子Partition,带有后裔三角化和凸形。

基类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

【问题讨论】:

    标签: c++ polymorphism virtual linker-errors functor


    【解决方案1】:

    看起来在您的 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 pure virtual [顺便说一句,您应该这样做],您将收到关于构造抽象类型的错误,而不是链接器错误,这可能对您更有用。从理论上讲,以下方法可以解决该问题:

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

    在这种情况下,输出是您所期望的。

    Base Int: 5
    Derived Int: 5
    

    但是,仍然存在一个问题:您不能从基类构造函数调用虚函数并获得多态行为……您最终会调用函数的基类版本,无论它是否已被覆盖。您必须等到对象完全构造后才能获得多态行为。

    基本上,在构造的每一步,对象就是正在构造的任何东西。尽管它最终会是一个Triangulate 对象,但在Partition 的构造函数中,它的行为类似于Partition。这意味着您仍然会遇到链接器错误。

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

    您也可以考虑延迟初始化...存储输入参数,并且仅在第一次执行仿函数时执行计算。之后,只需返回缓存的结果。这需要大量的额外开销,但具有完全安全的好处,无论您的最终继承图最终看起来如何。不过,有状态函子有自己的问题,具体取决于您计划如何使用此类。

    【讨论】:

    • 呃,那我想我没有得到函子,因为传递被覆盖的函数正是我想要做的=/。
    • @Griffin:您仍然可以使用构造函数,只是不能使用基类构造函数。请参阅我的扩展回复的下半部分。
    【解决方案2】:

    要么在内部class Partition 中有一个virtual void RunAlgorithm() 的方法体,要么将其声明为纯virtual:

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

    有一种特殊情况,当声明该类或其子对象时,任何虚函数都不能保持未实现(即使未使用该函数)。

    【讨论】:

    • 是的,但我希望基类构造函数使用在任一后代中定义的更新、重载的 RunAlgorithm() 函数。如果通过“将方法体添加到分区类”您的意思是简单地将 {} 作为内联函数,那么构造函数最终会使用该函数,而不是重载的函数。
    • 基指针指向派生类对象时使用重载函数。
    猜你喜欢
    • 1970-01-01
    • 2019-04-14
    • 2021-01-26
    • 2011-09-16
    • 2012-02-06
    • 1970-01-01
    • 2012-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多