Прошло много времени с тех пор, как я перешел на C ++, но я хочу убедиться, что придерживаюсь лучших практик, в том числе и правильности.
В настоящее время я создаю библиотеку кода для игрового фреймворка, и у меня есть следующие классы (обобщены для простоты):
class Screen
{
public:
Clear();
}
а также
class Game
{
private:
Screen* screen;
protected:
const Screen* GetScreen() const;
}
Для краткости я опустил все остальное, но достаточно сказать, что Game
класс отвечает за создание Screen
экземпляр класса. После того, как он создан, к нему можно получить доступ только через GetScreen()
метод.
Идея состоит в том, что при создании новой игры я бы унаследовал от этого базового класса, поэтому, например, во время цикла рендеринга я бы хотел очистить экран, чтобы перерисовать следующий кадр. Теперь в моих вышеприведенных определениях Clear()
метод не может быть вызван при использовании GetScreen()
метод, потому что это не const
, Метод clear фактически изменил бы внутреннюю работу класса (в силу того факта, что ранее отображаемое изображение было очищено), поэтому я оставил const
объявление вне определения. Если бы я сделал это const
тогда я должен был бы иметь некоторые из внутренней работы Screen
класс как mutable
но из того, что я прочитал, это не было бы очень правильным.
Итак, у меня есть две части к моему вопросу. Должен ли я изменить void Clear()
в void Clear() const
и сделать части внутренней работы mutable
?
Или это альтернатива, которая позволила бы мне сделать screen
член Game
класс устанавливается только один раз Game
класс, чтобы я мог получить доступ к неконстантным функциям-членам в течение остальной части времени выполнения программы.
Спасибо за любую помощь.
Понятие «const» полезно для указания того, что внутреннее состояние объекта не должно быть изменяемым.
void f(const Object& o)
{
// 'o' cannot be modified
}
Возможность пометить нестатический элемент как const
позволяет обеспечить соблюдение, что держатели const
ссылка на объект не может изменить внутреннее состояние. Например, в следующем сценарии Object
имеет аксессуар str
который возвращает ссылку на внутреннее состояние, которое имеет как неконстантные, так и константные перегрузки:
struct Object
{
// non-const: caller can modify internal state
std::string& str();
// const: caller cannot modify internal state
const std::string& str() const;
};
Неконстантная перегрузка допускает изменение внутреннего состояния, но может вызываться только при неконстантной ссылке на объект; Перегрузка const не позволяет изменять внутреннее состояние и может использоваться как с объектными, так и с неконстантными ссылками.
В сценарии, который вы представляете, Game
кажется монолитным одиночка объект, который содержит все в вашей программе. Если это так, то представляется сомнительным, что было бы полезно когда-либо передавать ссылку на Game
— или к объекту, производному от него — делая различие между const Game&
а также Game&
несколько спорный Если это так, const-правильность не имеет смысла, и вы избавите себя от головной боли, если просто сделаете все члены Game
неконстантная.
поскольку Game::screen
является закрытым, к нему не может получить доступ производный класс. Пока звонящий GetScreen()
может получить доступ к Screen
объект, он не может изменить то, что Game
хранится screen
указывает на. Таким образом, вы прекрасно с, например, обеспечивая эти две перегрузки:
class Game
{
Screen *screen;
protected:
const Screen* GetScreen() const;
Screen* GetScreen();
};
Ни один из них не позволяет производному классу изменять screen
сам указатель, поэтому он не может «сбросить» его, чтобы указать куда-то Game
не хотел бы, чтобы это указывало.
Я настоятельно рекомендую вам обновить ваши знания указателей в C ++. В наши дни почти нет причин использовать сырые указатели.
Чтобы ответить на вопрос напрямую, Game.GetScreen()
Функция будет хотеть ‘const’ по-разному, в зависимости от того, как именно вы хотите, чтобы она себя вела
Если вы вернете const Screen*
вы возвращаете указатель на объект Screen, который является постоянным (не может быть изменен). Обычно вы хотели бы сделать такую функцию const, позволяющей вызывать ее для объекта 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>
, Это можно использовать очень похоже на необработанный указатель, но позаботится о многих функциональных возможностях для вас. Тем не менее, вы хотите поделиться доступом к этому указателю экрана, поэтому вы, вероятно, хотите использовать std::shared_ptr<Screen>
и вернуться std::weak_ptr<Screen>
из функции get. Слабый_птр можно использовать, чтобы разрешить другим доступ, но ваш первоначальный игровой объект все еще владеет Экран объекта.
Короче говоря, сохраняйте объект Screen по значению и возвращайте ссылки на него.