【问题标题】:C++ Derived Class problemsC++派生类问题
【发布时间】:2010-09-10 10:54:14
【问题描述】:

我正在用 C++ 制作游戏,但我的派生类有问题。我有一个名为 GameScreen 的基类,它有一个没有语句的虚拟 void draw() 函数。我还有一个名为 MenuScreen 的派生类,它也有一个虚拟 void draw() 函数和一个来自 MenuScreen 的名为 TestMenu 的派生类,它也有一个 void draw() 函数。在我的程序中,我有一个 GameScreen 列表,我有一个 GameScreen 迭代器通过调用每个 GameScreens draw() 函数。

问题是我在 GameScreen 列表中放置了一个 TestMenu 对象。不是迭代器调用 TestMenu 的 draw() 函数,而是调用 GameScreen 类的 draw() 函数。有谁知道我如何调用 TestMenu 的 draw() 函数而不是 GameScreen 中的函数。

函数如下:

// Tell each screen to draw itself.
//gsElement is a GameScreen iterator
    //gsScreens is a list of type GameScreen
void Draw()
{
    for (gsElement = gsScreens.begin(); gsElement != gsScreens.end(); gsElement++)
    {
        /*if (gsElement->ssState == Hidden)
            continue;*/

        gsElement->Draw();
    }
}   

这是我的课程的副本:

class GameScreen {
public:
    string strName;
    bool bIsPopup;
    bool bOtherScreenHasFocus;
    ScreenState ssState;
    //ScreenManager smScreenManager;

    GameScreen(string strName){
        this->strName = strName;
    }

    //Determine if the screen should be drawn or not
    bool IsActive(){
        return !bOtherScreenHasFocus && 
            (ssState == Active);
    }

    //------------------------------------
    //Load graphics content for the screen
    //------------------------------------
    virtual void LoadContent(){
    }

    //------------------------------------
    //Unload content for the screen
    //------------------------------------
    virtual void UnloadContent(){
    }

    //-------------------------------------------------------------------------
    //Update changes whether the screen should be updated or not and sets
    //whether the screen should be drawn or not.
    //
    //Input:
    //  bOtherScreenHasFocus - is used set whether the screen should update
    //  bCoveredByOtherScreen - is used to set whether the screen is drawn or not
    //-------------------------------------------------------------------------
    virtual void Update(bool bOtherScreenHasFocus, bool bCoveredByOtherScreen){
        this->bOtherScreenHasFocus = bOtherScreenHasFocus;

        //if the screen is covered by another than change the screen state to hidden
        //else set the screen state to active
        if(bCoveredByOtherScreen){
            ssState = Hidden;
        }
        else{
            ssState = Active;
        }
    }

    //-----------------------------------------------------------
    //Takes input from the mouse and calls appropriate actions
    //-----------------------------------------------------------
    virtual void HandleInput(){
    }

    //----------------------
    //Draw content on screen
    //----------------------
    virtual void Draw(){
    }

    //--------------------------------------
    //Deletes screen from the screen manager
    //--------------------------------------
    void ExitScreen(){
        //smScreenManager.RemoveScreen(*this);
    }
};

class MenuScreen: public GameScreen{
public:
    vector <BUTTON> vbtnMenuEntries; 

    MenuScreen(string strName):GameScreen(strName){
    }

    virtual void Update(bool bOtherScreenHasFocus, bool bCoveredByOtherScreen){
        GameScreen::Update(bOtherScreenHasFocus, bCoveredByOtherScreen);

        for(unsigned int i = 0; i < vbtnMenuEntries.size(); i++){
            vbtnMenuEntries[i].IsPressed();
        }
    }

    virtual void Draw(){
        GameScreen::Draw();

        for(unsigned int i = 0; i < vbtnMenuEntries.size(); i++)
            vbtnMenuEntries[i].Draw();
    }

};

class testMenu : public MenuScreen{
public:
    vector<OBJECT> test;
    //OBJECT background3();
//  OBJECT testPic(512, 384, buttonHover.png, 100, 40, 100, 40);
//  BUTTON x(256, 384, buttonNormal.png, buttonHover.png, buttonPressed.png, 100, 40, test());
    bool draw;

    testMenu():MenuScreen("testMenu"){
        OBJECT background3(1, 1, 0, TEXT("background.png"), 1, 1, 1024, 768);
        OBJECT testPic(512, 384,0, TEXT("buttonHover.png"), 1, 1, 100, 40);
        test.push_back(background3);
        test.push_back(testPic);
        //background3.Init(int xLoc, int yLoc, int zLoc, LPCTSTR filePath, int Rows, int Cols, int Width, int Height)
        //test.push_back(background3);
    //  vbtnMenuEntries.push_back(x);
        draw = false;
    }

    void Update(bool bOtherScreenHasFocus, bool bCoveredByOtherScreen){
        MenuScreen::Update(bOtherScreenHasFocus, bCoveredByOtherScreen);
        //cout << "X" << endl;
        /*if(MouseLButton == true){
            testMenu2 t;
            smManager.AddScreen(t);
        }*/
    }

    void Draw(){
        //background3.Draw();
        test[0].Draw();
        test[1].Draw();
        MenuScreen::Draw();
    ///*if(draw){*/
    //  testPic.Draw();
    //}
}

/*void test(){
    draw = true;
}*/

};

【问题讨论】:

  • Curt 的回答是正确的。您应该将 GameScreen 的 Draw 函数设为纯虚拟,这样您就不会出现此类错误。

标签: c++ inheritance


【解决方案1】:

如果 gsScreens 是对象列表而不是指针列表(如您的代码所示),那么您不会存储您认为存储在其中的内容。

发生的事情是 -- 而不是将 TestMenu 放入列表中,您实际上是在使用编译器生成的复制构造函数构造一个新的 MenuScreen 并将此 MenuScreen 放入列表中。

C++ 是通过指针实现多态的,所以如果你没有指针,你就不会得到多态行为。

【讨论】:

  • Curts 建议听起来正确,将 gsScreens 更改为 GameScreen* 类型的列表。多态性需要指向对象的指针或引用。
  • 通过声明复制构造函数和 operator=private 来避免这种意外复制。这抑制了默认值,这些默认值是为了与 C 结构兼容。如果您希望一个类是可复制的(罕见的),那么两者都实现。确保它们与析构函数正确交互。
  • C++ 通过引用也是多态的。
  • 这是一个实现细节 ;) 在语言的语义中,引用和指针是不同的东西,因此在谈论多态性时应该提到两者。我不是说你错了,我只是在补充你几乎正确的答案;)
【解决方案2】:

要获得您所追求的多态行为并同时使用std::vector&lt;&gt;,您必须将指针存储到向量中的基类类型,而不是存储。此外,您必须记住在向量超出范围之前释放它们的内存。

例如:

#include <vector>
#include <algorithm>

struct Base
{
    virtual void Foo() = 0;
    virtual ~Base() { }
};

struct Derived1 : public Base
{
    void Foo() { }
};

struct Derived2 : public Base
{
    void Foo() { }
};

struct delete_ptr
{
    template <typename T>
    void operator()(T& p)
    {
        delete p;
        p = 0;
    }
};

int wmain(int, wchar_t*[])
{
    std::vector<Base*> items;
    items.push_back(new Derived1);
    items.push_back(new Derived2);

    Base& first = items.front();
    first.Foo(); // Will boil down to Derived1::Foo().

    Base& last = items.back();
    last.Foo(); // Will boil down to Derived2::Foo().

    std::for_each(items.begin(), items.end(), delete_ptr())
};

【讨论】:

    【解决方案3】:

    Curt 是绝对正确的,但我想向它提供更多信息。

    这个问题(存储基类对象,而不是指针)有时被称为“切片”。

    另外,我倾向于使用以下宏:

    #define DISALLOW_COPYING(X) \
        private: \
            X(const X &); \
            const X& operator= (const X& x)
    

    然后你把它放在你的类定义中:

    class Foo {
        // ...
        DISALLOW_COPYING(Foo);
    };
    

    如果另一个类尝试复制该对象,您将收到编译器错误(因为这些方法被声明为私有)。如果类本身尝试复制对象,您将收到链接器错误(因为方法没有实现)。

    【讨论】:

      【解决方案4】:

      Boost(www.boost.org,我推荐任何使用 C++ 编码的人使用的库)提供了一个不可复制的基类,可以做到这一点;这样你就不需要丑陋的宏了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-21
        相关资源
        最近更新 更多