强制在类的const const成员中在编译时

时间:2017-07-17 19:35:07

标签: c++ c++11 const sfml compile-time

我正在使用SFML创建一个Snake游戏,我有一个具体的类SnakeGame,它包含我班级的所有数据成员,例如窗口大小,游戏地图的大小,蛇的颜色,因此,我班级的内部成员看起来像这样:

class SnakeGame
{
private:
    //....
    const sf::Vector2u windowSize{400, 336};
    const sf::Color snakeColor {0, 0, 0};
    //...etc...
public:
    SnakeGame() : 
        renderWindow {sf::VideoMode{windowSize.x, windowSize.y}, /*other arguments*/ },
        snake {snakeColor, /*other arguments*/} 
        /*etc*/ 
    {}

然而,这不起作用,我得到一个看不见的窗口,让我相信参数还没有构建。在阅读了另一篇SO帖子之后,我读到const成员作为类的成员不会在编译时进行评估,而是在运行时实例化类,并且它们的行为方式与const完全相同变量。因此,当我在windowSize中实例化snakeColor时,会评估变量SnakeGamemain,因此我无法在我的类的初始化列表中使用它们。为了尝试解决这个困境,我决定切换到static constexpr变量,但不幸的是我意识到sf::Vector<T>没有constexpr构造函数,sf::Color也没有。当然,我想我可以切换到整数类型,例如static constexpr unsigned int作为窗口大小,但最终会变得重复,特别是对于颜色。所以我决定采取另一种方式。我所做的是创建另一个文件GameData.hpp,并在其中执行此操作:

namespace gd //For game data
{
    const sf::Vector2u windowSize{400, 336};
    const sf::Color snakeColor {0, 0, 0};
    //...etc...
}

但是,我不喜欢这个解决方案,因为即使现在在编译时评估这些数据,如果它们包含适当的头文件,也可以访问我的所有类,而不仅仅是SnakeGame。通过这种方式,我觉得我的班级的内部数据结构正在被揭示。这导致了我的问题,即:是否有办法强制在编译时对类的const成员进行评估,使得这些变量可以在初始化列表中使用,并保证他们会构建吗?

1 个答案:

答案 0 :(得分:2)

这里有几个选项,所有选项都有权衡。我把它们按照我认为最好的最差的顺序排列,但你的权衡可能会有所不同。

  • 重构代码,使颜色和窗口大小不是全局常量。作为一名运动员,我想要一条绿色的蛇,而不是一条黑色的蛇,我想要一块两倍大的板子。这可能涉及将它们保留为成员变量,但这次不是常量,并且可能重新排序它们,以便您可以在其他成员变量的初始化中使用它们。
  • 将编译器设置为C ++ 17模式,并使snakeColor成为inline static const变量。如果你想要一个只有头文件库并且可以使用C ++ 17,这是最好的选择。
  • 将其设为const static数据成员,并将const sf::Color SnakeGame::snakeColor {0, 0, 0}添加到一个.cpp文件中。如果您不需要仅包含标头的库,这是最佳选择。
  • 制作getColor static const sf::Color& getSnakeColor() 方法,其中包含static常量,然后返回。 static关键字在函数内部的含义略有不同,然后您可以在此处执行所需操作。但是,这意味着您需要更改使用方式的语法。
  • 使用命名空间,如答案中所建议的那样。如果其他所有内容都在namespace snake中,那么您将这些变量放在snake::details中,那么按照惯例,人们就会知道他们不应该看到那里。与其他选项不同,这不是强制执行的。
  • 使用SFML提交功能请求,以使Vector2uColor成为文字类型,以便您可以使用constexpr。显然这需要更长时间,并且不能保证有效。
  • 重新排序课程,使成员处于更有用的顺序as suggested by VTT in the comments。这与我说我最喜欢的第一个选项相同,但是将它们保持为具有相同值的常量,用于每个类的实例。我把它放在列表底部的原因是,这意味着你的类的每个副本都会带有一个颜色和窗口大小的副本,当你只需要一个。

我可能还有其他选择。例如,有一种涉及帮助器模板类的方法,您可以使用它来获取仅包含头的库,不需要inline成员变量支持,也无需切换到函数调用来访问数据,以及{强制执行{1}}说明符。我不太记得如何使它工作,而且它比通常值得复杂。