Reputation: 359
I write games in C++ with SDL. I wrote games in C for more than a year and now I've been writing in C++ for the last 7 months. I'm trying to avoid almost all global variables and move to a system of ownership, where every object is owned by something else. This way I can just manage the life of classes who have shared_ptr members and almost never worry about freeing pointers.
For example, my game is a class with its subsystems.
class Game
{
public:
Game();
~Game();
void runFrame();
std::shared_ptr<Display> display;
std::shared_ptr<Audio> audio;
std::shared_ptr<Save> save;
};
But I run into messy looking nested classes, like my audio class below.
class Audio
{
public:
Audio(Game& parent);
~Audio();
struct MusicFiles;
struct SfxFiles;
std::shared_ptr<MusicFiles> musicFiles;
std::shared_ptr<SfxFiles> sfxFiles;
private:
class Music
{
public:
class File
{
public:
File(Music& parent, std::string fileAddr);
~File();
void play();
void fadeIn();
void stop();
private:
Music& _parent;
std::string addr;
Mix_Music* chunk;
};
Music(Audio& parent);
~Music();
void setVolume();
private:
Audio& _parent;
bool _enabled;
int _volume;
};
class Sfx
{
public:
class File
{
public:
File(Sfx& parent, std::string fileAddr);
~File();
void play();
void stop();
private:
Sfx& _parent;
std::string addr;
Mix_Chunk* chunk;
int channel;
};
Sfx(Audio& parent);
~Sfx();
void setVolume();
private:
Audio& _parent;
bool _enabled;
int _volume;
};
Game& _parent;
Music _music;
Sfx _sfx;
};
I have nested classes because I dislike having to write "Music" or "Sfx" in every function name, like setMusicVolume(), setSfxVolume(), setMusicHook(), setSfxHook(), etc. etc. I can pull the nested classes out but Music and Sfx only need to exist within the Audio class. I'd rather reorganize everything and have a better design.
Do you have any better design suggestions?
Upvotes: 1
Views: 1409
Reputation: 973
Organize your various classes into a namespace
to group them, instead of a single class. This thing will become very hard to maintain once it grows. You want to use your classes to encapsulate functionality and provide a simple and comprehensive interface to the 'outside world'. Typically, each class will get its own header file (.h
or .hpp
) and compilation unit (.cpp
).
Accept that there are many good reasons for using getter and setter functions - they are fundamental for good encapsulation. If you really have to, you can always use friend class Audio
in your nested classes to make private
and protected
members visible to Audio
, but not to all other classes in your project that may (at some time in the future, maybe) include these classes.
You say that you dislike writing audio.setMusicVolume(0.8f)
instead of audio.music.setMusicVolume(0.8f)
. Some may say that audio.getMusic().setVolume(0.8f)
would be a good compromise, but Demeter disagrees. By using wrapper functions like setMusicVolume
, every other class only needs to know about and communicate with Audio
while Music
is strictly internal and only known by Audio
itself. If you use function chaining, you lose this advantage and expose Music
and Sfx
to the world - which is not necessarily a bad thing. If you want to stick to your syntax, my suggestion would be to keep the public interfaces very small and use friend
to expose more functionality to Audio
, if you need to.
Upvotes: 4
Reputation: 283733
FYI, in addition to using namespaces, nested types can be placed out of line, just like member functions, by using their qualified name. So you can do:
class Audio
{
public:
Audio(Game& parent);
~Audio();
struct MusicFiles;
struct SfxFiles;
std::shared_ptr<MusicFiles> musicFiles;
std::shared_ptr<SfxFiles> sfxFiles;
private:
class Music;
};
in another file
#include "Audio.h"
class Audio::Music
{
public:
class File;
Music(Audio& parent);
~Music();
void setVolume();
private:
Audio& _parent;
bool _enabled;
int _volume;
};
Upvotes: 2