質問

It's been a while since I've jumped into C++ but I want to make sure I'm sticking to best practices when I do, including being const-correct.

I'm currently building a library of code for a game framework and I have the following classes (summarised for simplicity):

class Screen
{
public:
    Clear();
}

and

class Game
{
private:
    Screen* screen;

protected:
    const Screen* GetScreen() const;
}

I've omitted the rest for brevity but suffice it to say, the Game class is responsible for the creation of the Screen class instance. After it is created, it should only be accessed through the GetScreen() method.

The idea is, that when creating a new game, I would inherit from this base class, so as an example, during a render loop I would want to clear the screen to redraw the next frame. Now, in my above definitions, the Clear() method is not allowed to be called when using the GetScreen() method because it is not const. The clear method would actually change the internal workings of the class (by virtue of the fact that the previously displayed image is cleared) so that is why I left the const declaration out of the definition. If I made it const then I would have to have some of the inner workings of the Screen class as mutable but from what I've read, this would not be very const-correct.

So, I have two parts to my question. Should I change void Clear() to void Clear() const and make parts of the inner workings mutable?

Or is the an alternative that would allow me to make the screen member of the Game class only settable once by the Game class so that I can access the non-const member functions during the rest of the program's run time.

Thanks for any help.

役に立ちましたか?

解決 2

The concept of 'const' is useful to indicate that an object's internal state should not be modifiable.

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

The ability to mark a non-static member as const allows you to enforce that holders of a const reference to the object cannot modify the internal state. For example, in the following scenario, Object has an accessor str that returns a reference to a internal state, which has both non-const and const overloads:

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

    // const: caller cannot modify internal state
    const std::string& str() const;
};

The non-const overload allows modification of internal state, but can be called only on a non-const reference to the object; the const overload does not allow modification of internal state and can be used with both const and non-const object references.

In the scenario you present, Game appears to be a monolithic singleton object that contains everything in your program. If so, it seems questionable that it would be useful to ever pass a reference to a Game - or to an object derived from it - making the distinction between const Game& and Game& somewhat moot. If this is the case, const-correctness has no utility and you will save yourself a headache if you simply make all members of Game non-const.

他のヒント

Since Game::screen is private, it cannot be accessed by the derived class. While a caller of GetScreen() can access the Screen object, he cannot modify what the Game's stored screen points to. So you're perfectly fine with e.g. providing these two overloads:

class Game
{
  Screen *screen;

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

None of them allows the derived class to modify the screen pointer itself, so it cannot "reset" it to point somewhere Game wouldn't want it to point.

I strongly suggest you update your knowledge of pointers in C++. There is almost no reason to use raw pointers these days.

To answer the question directly, the Game.GetScreen() function would want to 'const' differently depending on how exactly you wish for it behave.

If you return a const Screen* you are returning a pointer to a Screen object that is constant (cannot be modified). You would normally wish to make such a function const allowing it to be called on a Game object that is const it self. If somebody has a non-const Game object, you can allow them to get the Screen object that they can then modify by simply returning a Screen*.

That said, I return to my opening point, you shouldn't use raw pointers.

Initially, you should just have the Screen object stored by value, and return references, giving you a class along the lines of:

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

This may look like you are copying the Screen object to return it, but the return types ensure you are returning a reference to the same screen object, rather than a copy.

If you really do need dynamic creation of the Screen object (ie, you cannot create it either before or as you create the Game object) then you should use a std::unique_ptr<Screen>. This can be used very much like a raw pointer, but will take care of a lot of the functionality for you. However, you wish to share access to this Screen pointer, so you probably want to use std::shared_ptr<Screen> and return std::weak_ptr<Screen> from the the get function. The weak_ptr can be used to allow others access, but your initial Game object still owns the Screen object.

Long story short, store the Screen object by value, and return references to it.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top