【问题标题】:C++ Only Allow a member variable to be set onceC++ 只允许一个成员变量被设置一次
【发布时间】:2024-01-15 08:15:01
【问题描述】:

自从我开始接触 C++ 已经有一段时间了,但我想确保我在这样做时坚持最佳实践,包括 const 正确。

我目前正在为游戏框架构建一个代码库,并且我有以下类(为简单起见进行了总结):

class Screen
{
public:
    Clear();
}

class Game
{
private:
    Screen* screen;

protected:
    const Screen* GetScreen() const;
}

为简洁起见,我省略了其余部分,但我只想说,Game 类负责创建 Screen 类实例。创建后,只能通过GetScreen()方法访问。

这个想法是,当创建一个新游戏时,我将从这个基类继承,例如,在渲染循环期间,我想清除屏幕以重绘下一帧。现在,在我上面的定义中,在使用GetScreen() 方法时不允许调用Clear() 方法,因为它不是const。 clear 方法实际上会改变类的内部工作方式(由于先前显示的图像被清除),所以这就是我将const 声明排除在定义之外的原因。如果我做到了const,那么我将不得不将Screen 类的一些内部工作作为mutable,但从我所读到的内容来看,这不是非常正确的。

所以,我的问题有两个部分。我是否应该将void Clear() 更改为void Clear() const 并制作部分内部工作mutable

或者是允许我使Game 类的screen 成员只能由Game 类设置一次的替代方法,这样我就可以在其余时间访问非常量成员函数程序的运行时间。

感谢您的帮助。

【问题讨论】:

  • 您似乎希望 screen 成为 Screen&Screen* const 而不是 const Screen*

标签: c++ constants mutable const-correctness


【解决方案1】:

由于Game::screen 是私有的,它不能被派生类访问。虽然GetScreen() 的调用者可以访问Screen 对象,但他不能修改Game 存储的screen 指向的内容。所以你很好,例如提供这两个重载:

class Game
{
  Screen *screen;

protected:
  const Screen* GetScreen() const;
  Screen* GetScreen();
};

它们都不允许派生类修改 screen 指针本身,因此它不能“重置”它以指向某个 Game 不希望它指向的地方。

【讨论】:

    【解决方案2】:

    'const' 的概念有助于指示对象的内部状态不应被修改。

    void f(const Object& o)
    {
        // 'o' cannot be modified
    }
    

    将非静态成员标记为const 的能力允许您强制对对象的const 引用的持有者不能修改内部状态。例如,在以下场景中,Object 有一个访问器 str,它返回对内部状态的引用,该状态同时具有非 const 和 const 重载:

    struct Object
    {
        // non-const: caller can modify internal state
        std::string& str();
    
        // const: caller cannot modify internal state
        const std::string& str() const;
    };
    

    非常量重载允许修改内部状态,但只能在对对象的非常量引用上调用; const 重载不允许修改内部状态,并且可以与 const 和非 const 对象引用一起使用。

    在您呈现的场景中,Game 似乎是一个整体的 singleton 对象,其中包含您程序中的所有内容。如果是这样,那么传递对Game 或从它派生的对象的引用是否有用似乎值得怀疑,这使得const Game&Game& 之间的区别在某种程度上没有实际意义。如果是这种情况,则 const-correctness 没有任何用处,如果您简单地将 Game 的所有成员设为非 const,您将不会感到头疼。

    【讨论】:

    • 我想我会按照你的建议去做,让Game 的成员成为非常量。虽然不是真正的单例,但它实际上是一个入口点。例如。主函数将创建一个新的Game 对象并调用它的唯一公共方法Run(),其余的从那里处理。 Game 的派生类将负责任何其他所需的对象,并且不需要访问游戏。当我不需要将事物设为 const 时,我可能只是想多了。
    【解决方案3】:

    我强烈建议您更新您对 C++ 指针的了解。现在几乎没有理由使用原始指针。

    要直接回答这个问题,Game.GetScreen() 函数会根据您希望它的行为方式不同地“const”。

    如果您返回const Screen*,您将返回一个指向不变(无法修改)的 Screen 对象的指针。您通常希望使这样的函数 const 允许在自身为 const it 的 Game 对象上调用它。如果某人有一个非常量的 Game 对象,您可以允许他们获取 Screen 对象,然后他们可以通过简单地返回 Screen* 来修改该对象。

    也就是说,我回到我的开场白,你不应该使用原始指针。

    最初,您应该只将 Screen 对象按值存储,并返回引用,从而为您提供一个类:

    Class Game{
        Screen screen;
    public:
        const Screen& GetScreen() const{ return screen; }
        Screen& GetScreen(){ return screen; }
    };
    

    这可能看起来您正在复制 Screen 对象以返回它,但返回类型确保您返回的是对同一屏幕对象的引用,而不是副本。

    如果您确实需要动态创建 Screen 对象(即,您不能在创建 Game 对象之前或同时创建它),那么您应该使用std::unique_ptr<Screen>。这可以非常像原始指针一样使用,但会为您处理很多功能。但是,您希望共享对此 Screen 指针的访问权限,因此您可能希望使用 std::shared_ptr<Screen> 并从 get 函数返回 std::weak_ptr<Screen>。 weak_ptr 可用于允许其他人访问,但您的初始 Game 对象仍拥有 Screen 对象。

    长话短说,按值存储 Screen 对象,并返回对它的引用。

    【讨论】: