【问题标题】:Force in class const member of class to be evaluated at compile time强制在编译时评估类的类 const 成员
【发布时间】:2017-12-22 11:12:28
【问题描述】:

我正在使用 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 时,会评估变量windowSizesnakeColor,因此我不能在我的类的初始化程序列表中使用它们。为了尝试解决这个困境,我决定切换到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 类的成员在编译时进行评估,这样这些变量可以在初始化列表中使用,并保证它们会建好了吗?

【问题讨论】:

  • 你为什么不简单地使用static成员(非constexpr)?
  • 或以正确的顺序重新排列您的成员。
  • 您还可以根据它们将这些字段的声明移到字段声明之上,因为类字段是按照您声明它们的顺序构造的。
  • @JérémiPanneton 我会这样做,但我想初始化类中的成员。
  • @Jarod42 我已经按照你说的做了,而且有效!谢谢!

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


【解决方案1】:

这里有几个选项,所有选项都需要权衡取舍。我按照我认为最好到最差的粗略顺序对它们进行了排名,但您的权衡可能会有所不同。

  • 重构您的代码,使颜色和窗口大小不是全局常量。作为一名玩家,我想要一条绿蛇,而不是黑蛇,我想要一个两倍大的棋盘。这可能涉及将它们保留为成员变量,但这次不是常量,并且可能对它们进行重新排序,以便您可以在其他成员变量的初始化中使用它们。
  • 将编译器设置为 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 成员变量支持,也无需切换到函数调用来访问数据,而private说明符被强制执行。不过,我不太记得如何让它发挥作用,而且它比通常的价值要复杂得多。

【讨论】:

  • Visual Studio Community Edition 2017 是否支持 C++17 功能,还是我必须启用它?
  • 我很确定您可以启用对某些 C++17 功能的支持,但我不知道这是否是其中之一,我也不知道该怎么做。
  • 你知道吗,我决定改变主意,转而使用第一个要点。你是对的,这应该是最好的选择。
猜你喜欢
  • 2016-11-29
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-22
  • 1970-01-01
  • 2012-12-26
  • 2017-05-26
  • 1970-01-01
相关资源
最近更新 更多