模板 class 和嵌套 class C++

Template class and nested class C++

我对 typename SnakeGame 有疑问。我想知道如何将 SnakeGame 设为 class KeyboardEvents 中的全局类型。现在像 DirectionKeyboard 这样的嵌套 class 不知道 SnakeGame 是什么类型,因为它只看到 KeyboardEvents<SnakeGame> 类型。我不知道怎么改:P

这是错误:

不知道参数 1 从 'KeyboardEvents SnakeGame>&' 到 'SnakeGame&' 的转换

非常感谢帮助。

keyboardEvents.hpp

#include<SFML/Graphics.hpp>

template <typename SnakeGame>
class KeyboardEvents {
public:
    virtual ~KeyboardEvents() = default;

protected:
    class DirectionKeyboardEvent{
    public:
        virtual ~DirectionKeyboardEvent() = default;
        virtual void direction(SnakeGame&) = 0; // error no know conversion 
    };      

    class GoRight : public DirectionKeyboardEvent {
    public:
        void direction(SnakeGame& snakeObj) {
            snakeObj.snake[0].xCoor+=1;
        }
    };

    class GoRight : public DirectionKeyboardEvent {
    public:
        void direction(SnakeGame& snakeObj){
            snakeObj.snake[0].xCoor += 1;
        }
    };

    class GoLeft : public DirectionKeyboardEvent{
    public:
        void direction(SnakeGame& snakeObj){
            snakeObj.snake[0].xCoor-=1;
        }                     
    };                            

    class GoUp:public DirectionKeyboardEvent{
    public:
        void direction(SnakeGame& snakeObj){
            snakeObj.snake[0].yCoor-=1;
        }                   
    };

    class GoDown : public DirectionKeyboardEvent{
    public:
        void direction(SnakeGame& snakeObj){
            snakeObj.snake[0].yCoor+=1;
        }
    };

    std::map<sf::Keyboard::Key,  std::shared_ptr<DirectionKeyboardEvent>> mapOfDirects;

    void initializeDirectionMap() {
        mapOfDirects[sf::Keyboard::Right] = std::shared_ptr< DirectionKeyboardEvent >(new GoRight);
        mapOfDirects[sf::Keyboard::Left] = std::shared_ptr<DirectionKeyboardEvent>(new GoLeft);
        mapOfDirects[sf::Keyboard::Up] = std::shared_ptr<DirectionKeyboardEvent>(new GoUp);
        mapOfDirects[sf::Keyboard::Down] = std::shared_ptr<DirectionKeyboardEvent>(new GoDown);
    }

    void chooseMethodFromKeyboardArrows(sf::Keyboard::Key codeFromKeyboard) {
        auto iterator = mapOfDirects.find(codeFromKeyboard);

        if(iterator!=mapOfDirects.end()){
            iterator->second->direction(*this);//left , right,up , down, pause
            mainDirection=codeFromKeyboard;
        } else {
            mapOfDirects[mainDirection]->direction(*this);
        }
    }
};

这里是 class 我用 KeyboardEvents ~ snakeGame.hpp

#include"keyboardEvents.hpp"

class SnakeGame:public Screen, public KeyboardEvents<SnakeGame> {
    public:
        SnakeGame(int size=16, int width=15, int height=15, int timeDelay=60000)
            : Screen(size, width, height), KeyboardEvents<SnakeGame>(), timeDelay(timeDelay) {}
};

您的设计似乎有点过于复杂。我认为之所以如此,也许是因为您在进行过程中正在设计它。有时先坐下来思考这些事情会有所帮助,必要时在白板上画框和画线。

无论如何,这不是对您问题的直接回答,而是基于我猜测您正在尝试做的事情的替代建议。

在我看来,您正在尝试实现一些通用键盘输入处理程序并将其绑定到您的游戏中。有可能我完全错了,但如果不是,请考虑这样的事情。首先,接收键盘事件的通用接口。它不需要是模板,这不是一个很好的模板用例:

class KeyboardEventHandler {
public:
    enum Direction { Left, Right, Up, Down };
    virtual ~KeyboardEventHandler () { }
    virtual void onDirectionKey (Direction d) = 0;
};

现在处理键盘事件的 SnakeGame 可以继承它并实现它自己的 SnakeGame 特定逻辑:

class SnakeGame : public KeyboardEventHandler {
public:
    void onDirectionKey (Direction d) {
        switch (d) {
        case Up: ...
        case Down: ...
        case Left: ...
        case Right: ...
        }
    }
};

然后,无论您拥有的任何实际处理键盘事件并驱动所有这些的代码都可以与 KeyboardEventHandler * 一起使用,它可以是 SnakeGame,也可以是其他任何东西您决定将来使用它。

这只是组织的一种可能性。例如,您可以改为这样构建它,分解 KeyboardEvent,这可以简化将来的添加:

class KeyboardEvent {
public:
    enum Direction { Left, Right, Up, Down };
    Direction getDirection () { ... } // or whatever
};

class KeyboardEventHandler {
public:
    virtual ~KeyboardEventHandler () { }
    virtual void onEvent (KeyboardEvent &event) = 0;
};

SnakeGame为:

class SnakeGame : public KeyboardEventHandler {
public:
    void onEvent (KeyboardEvent &event) {
        ...
    }
};

如果你愿意,你可以将这些东西命名为 Direction / onDirectionKey 之外的其他东西,我从你的例子中选择了它,但只是让它在语义上合适并且也很方便(例如,如果你计划扩展它以包括不仅仅是箭头)。但无论如何,这不是重点。

还有 10 亿种其他方法可以给这只猫蒙皮,但重要的要点是:如果你想为某些东西制作一些通用界面,你真的做不到依赖于继承它的具体细节,否则你就违背了使它通用的目的。 在那种情况下,要么它不是通用基础/继承的好例子,要么你已经只是搞砸了设计,需要坐下来重新考虑。

记住:您的目标不是向代码中添加尽可能多的 类 之类的东西;你不会想要继承高分。你的目标是保持你的代码干净、可读、可维护、正确、可能可重用,并让你自己的工作更轻松。这些是工具,不要因为有了它们就使用它们,而是在需要它们时使用它们,让您的生活更轻松。

然而,综上所述,尽管这是一个有趣的练习,但对于您的特定应用程序来说这仍然是过分的。老实说,在您的特定情况下,我只是把所有的继承等等都扔掉,然后做类似的事情:

class SnakeGame {
public:
    void handleKeyPress (char c) {
        // ... do the right thing here
    }
}

并完成它。

在你尝试调用DirectionKeyboardEvent::direction里面的KeyboardEventsclass.

即使你放置了一个恰好是 child class 的模板参数,编译器也没有办法提前知道 KeyboardEvents<SnakeGame> 绝对会被扩展class SnakeGame.

我的意思是,可以编写以下代码:

KeyboardEvents<SnakeGame> keyboardEvents;

keyboardEvents.chooseMethodFromKeyboardArrows(/* some key */);

在那种情况下,keyboardEventsSnakeGame 没有太大关系。事实上根本没有创建 SnakeGame 个实例!编译器是对的,调用direction的函数chooseMethodFromKeyboardArrows是错误的,假设一个KeyboardEvents<SnakeGame>是一个SnakeGame.

继承以相反的方式工作:SnakeGame 确实是 KeyboardEvents<SnakeGame>。另一种方式是错误的。

我可以告诉你如何"to make it work",但这里需要警告:你过度使用了继承,并且在KeyboardEvent。你真的应该试着重新安排周围的事情,否则你最终会一团糟。


解决方案"make it work"

由于您使用的是 CRTP,因此您可以告诉编译器 KeyboardEvents<SnakeGame> 在绝对所有情况下确实被 SnakeGame 扩展。如果真是这样,你可以 static_cast 你的基础 class 到 child class:

if(iterator!=mapOfDirects.end()){
    // Notice the presence of the cast here
    iterator->second->direction(static_cast<SnakeGame&>(*this));
    mainDirection=codeFromKeyboard;
}

稍微好一点的方案

您也可以使用蛇的现有实例 class 作为参数。

void chooseMethodFromKeyboardArrows(sf::Keyboard::Key codeFromKeyboard, SakeGame& game){
    auto iterator = mapOfDirects.find(codeFromKeyboard);

    if(iterator!=mapOfDirects.end()){
        iterator->second->direction(game);
        mainDirection=codeFromKeyboard;
    } else {
        mapOfDirects[mainDirection]->direction(game);
    }
}

但是,最好的办法是不要让 SnakeGame 扩展 KeyboardEvent,而是将其包含在 class 中:

struct SnakeGame : Screen {
    KeyboardEvent<SnakeGame> event;

    void callEvents() {
        event.chooseMethodFromKeyboardArrows(/* some key */, *this);
    }
};

这是给你的作业: 尝试使 class KeyboardEvent 不是模板。我相信您可以找到一种方法来传递您的 class 而无需使用模板,同时仍然可以直接访问您的 class SnakeGame,而无需强制转换或接口。