C++ 编译时要计算的类的类常量成员中的Force

C++ 编译时要计算的类的类常量成员中的Force,c++,c++11,constants,sfml,compile-time,C++,C++11,Constants,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

我正在使用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
变量完全相同。因此,当我在
main
中实例化
SnakeGame
时,会对变量
windowSize
snakeColor
进行计算,因此我无法在类的初始值设定项列表中使用它们。为了尝试解决这个困境,我决定切换到
static constexpr
变量,但不幸的是,我意识到
sf::Vector
没有
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
成员在编译时进行求值,从而使这些变量可以在初始值设定项列表中使用,同时保证它们将被构造出来?

这里有几个选项,所有这些选项都有折衷之处。我把它们按我认为最好的最坏的顺序排列,但是你的权衡可能不同。

  • 重构代码,使颜色和窗口大小不是全局常量。作为一名球员,我想要一条绿色的蛇,而不是黑色的,我想要一块两倍大的棋盘。这可能涉及将它们保留为成员变量,但这次不是常量,还可能对它们重新排序,以便在初始化其他成员变量时使用它们
  • 将编译器设置为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
    关键字在函数中的含义稍有不同,然后您可以在函数中执行您想要的操作。但是,这意味着您需要更改如何使用它的语法
  • 按照您在回答中的建议,使用名称空间。如果其他所有内容都在
    名称空间snake
    中,那么您将这些变量放在
    snake::details
    中,按照惯例,人们将知道不应该查看这些变量。不过,与其他选项不同,这并不是强制的
  • 使用SFML提交一个功能请求,以将
    Vector2u
    Color
    转换为文本类型,这样您就可以使用
    constexpr
    。显然,这需要更长的时间,而且不能保证有效
  • 对类重新排序,以便将成员按更有用的顺序排列。这与第一个选项相同,我说我最喜欢第一个选项,但将它们作为常量保留,并对类的每个实例使用相同的值。我之所以把它放在列表的底部,是因为这意味着你的每一个类的副本都会有一个颜色和窗口大小的副本,而实际上你只需要一个

我可能忘了还有其他选择。例如,有一种方法涉及一个助手模板类,您可以使用它来获取一个只包含标题的库,而不需要
内联
成员变量支持,也不需要切换到函数调用来访问数据,并且强制使用
私有
说明符。不过,我不太记得如何让它工作,而且它比通常的价值更复杂。

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

  • 重构代码,使颜色和窗口大小不是全局常量。作为一名球员,我想要一条绿色的蛇,而不是黑色的,我想要一块两倍大的棋盘。这可能涉及将它们保留为成员变量,但这次不是常量,还可能对它们重新排序,以便在初始化其他成员变量时使用它们
  • 将编译器设置为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
    关键字在函数中的含义稍有不同,然后您可以做什么