C++ learning not to use globals

Started by
6 comments, last by ToohrVyk 15 years, 6 months ago
I tend to use globals alot, and so many people bash the use of them that I'd like to see how one is meant to do such things. As a simple example using my library, i have a font instance declared globally, initiated in the main function, then used in various functions related to rendering.

dglf::Font f_mtcorsva_14;

void cb_main();
int main()
{
   ...other initiation stuff
   f_mtcorsva_14 = dglf::Font("mtcorsva.ttf",14);

   dgl::mainFunc(cb_main);
   
   dgl::mainloop();
   return 0;
}

void cb_main()
{
   ...other rendering stuff
   f_mtcorsva_14.render("hello",0,14);
}
How would I be suggested to modify such a usage to avoid declaring the font object globally?
Advertisement
Ok, so the point you are trying to make is that this procedure is made more difficult by the fact that you have to pass a function pointer to run as your main function. What I recommend here is to use Inheritance instead of function pointers.

Define an interface like so:
class IMainLoop{    virtual void mainCallback() = 0;};


Then implement the interface:
class MyMainLoop : public IMainLoop{public:    MyMainLoop(dglf::Font myFontIn) : myFont(myFontIn)    {    }    virtual void mainCallback()    {        myFont.render("hello",0,14);    }private:    dglf::Font myFont;};


Your dgl::mainFunc will take a pointer to an IMainLoop and execute "mainCallback()" on the instance passed.

The replacement of what you posted should look like:
class MyMainLoop : public IMainLoop{public:    MyMainLoop(dglf::Font myFontIn) : myFont(myFontIn)    {    }    virtual void mainCallback()    {        myFont.render("hello",0,14);    }private:    dglf::Font myFont;};int main(){   ...other initiation stuff   IMainLoop* loopCB = new MyMainLoop(dglf::Font("mtcorsva.ttf",14));   dgl::mainFunc(loopCB);   dgl::mainloop();   return 0;}


This method won't be an option however if this "dgl" thing is not something you created or can modify easily, if you are stuck with a C library interface then theres not always an OOP choice when you are interacting with it. You might pull it off using a few static functions, etc, but as anybody knows member function pointers can be a pretty tricky thing to get working at all.
For all following that model could end up with many many classes in that form; which I would usually see as being 'over the top OOP' , I actually quite like that :)

Although it wouldn't be necessary to call the dglf::Font constructor from within 'main' that could be done within the MyMainLoop constructor which also avoids the nuisance of potentially having a silly number of parameters following your precise model.
Working with functions in a first class way is the pitts in c++. However if dlg::mainfunc can work generically then an approach would be to bind the font argument to the existing function, before passing the new function to mainFunc;


void cb_main( dglf::Font &font);

int main()
{
.... other initiation stuff
dglf::Font f_mtcorsva_14 = dglf::Font("mtcorsva.ttf",14);

boost::function< void () > cb_main1 = boost::bind( &cb_main, f_mtcorsva_14);
dgl::mainFunc( cb_main1);

dgl::mainloop();
return 0;
}

void cb_main( dglf::Font &font)
{
...other rendering stuff
font.render("hello",0,14);
}
I'm not sure that you would always want to avoid the global in this case (although it is absolutely poorly named for a global font.. think along the lines of 'defaultfont', 'errorfont', and so on..)

The problem here is that you arent really doing object oriented programming and without OO it often becomes a complexity nightmare to avoid globals. The font should usualy be a property of the rendering surface and you would hand that rendering surface off to functions which need to render (or you make that surface itself global instead.)

I am not in completely agreement with the anti-global crowd. Sometimes it just makes more sense from a complexity standpoint to use a global. I think of all the code I have written to be usable in multiple projects, and how little of it actualy gets used in multiple projects (code re-use is more of a fiction than a reality in my case.) For me, that extra OO sugar often isnt worth the extra effort, especialy since I grew up in a world where structure programming languages dominated and continue to think within those terms because of it.
There's no magic trick to it. You just pass the variable to the functions that need to use it. I don't know what your dgl namespace is from, but assuming it's your own code, you can easily modify the function signatures. (If you can't change it, so mainFunc *must* call a parameter-less void function, you're kind of screwed... [grin]

Anyway, if you're not calling cb_main directly, but passing a function pointer instead, I'd replace it with a functor: (that's not the only solution, but imo the cleanest)
struct cb_main {  cb_main(const dglf::Font& font) : font(font) {}  void operator()()  {     ...other rendering stuff     f_mtcorsva_14.render("hello",0,14);  }private:  dglf::Font font};int main(){   ...other initiation stuff   dglf::Font f_mtcorsva_14 = dglf::Font("mtcorsva.ttf",14);   dgl::mainFunc(cb_main(f_mtcorsva_14));      dgl::mainloop();   return 0;}

A few things:
- f_mtcorsva_14 (so, a 14pt font, mtcor style?) Hungarian is bad for a reason. We don't care what an instance is, we want to know what the purpose of this font instance is. Let's assume it's used to render status text.
- Why is cb_main a function pointer?

// So, we have a Game object, which represents a gameclass Game {public:  Game()    : status_font("mtcorsva.ttf",14)  {}  void run() {    ..    status_font.render("hello",0,14);    ..  }private:  // Font used to render status text  dglf::Font status_font;};int main(){  Game game;  game.run();  return 0;}


And that's it.

The next question is obviously, doesn't this make it hard to customize things? Let's say we don't want to hard-code values:
class Config {  Config(...);  int as_int(const std::string & key, int default_value);  std::string as_string(const std::string & key, const std::string & default_value);  ...};class Game {public:  Game(const Config & cfg)    : status_font(       cfg.as_string("status_font", "mtcorsva.ttf"),       cfg.as_int("status_font_size", 14))  {}...}int main(int argc, char **argv){  try {    Config arg_cfg(argc, argv);    Game game(arg_cfg);    game.run();  } catch (std::exception & e) {    std::cout << e.what();  }  return 0;}


Now, let's add resource manager:
class ResourceManager {  ResourceManager(const Config & cfg)     : resource_path(cfg.as_string("resource_path", ".")  {}};class Game {public:  Game(const Config & cfg)    : status_font(       cfg.as_string("status_font"mtcorsva.ttf"),       cfg.as_int("status_font_size", 14))    , rmg(cfg)  {}...private:  ResourceManager rmg;}// main remains unchanged


And so on....
Don't avoid globals because you think globals are bad. This often leads to removing globals without solving the problem that globals created (using Singletons is a classic examples of the cure being worse than the disease).

The main problem with globals is that they make your code harder to document, which in turn makes it harder to debug and extend because it's too complex. Your aim should be to reduce that complexity by designing your code in a way that reduces the amount of information that needs to be documented, which implies that you write code which interacts less with its environment. Instead of avoiding globals, you will simply settle for a solution that happens not to use globals.

Quote:Original post by Rockoon1
The problem here is that you arent really doing object oriented programming and without OO it often becomes a complexity nightmare to avoid globals.
Try functional programming [smile]

Quote:I think of all the code I have written to be usable in multiple projects, and how little of it actualy gets used in multiple projects (code re-use is more of a fiction than a reality in my case.)
A lot of people take "code reuse" to mean "when you start a new project, you will be able to use a lot of code from your previous project". This is not the intended meaning—sure, you get to do that as well, but the core idea of code reuse is to use the same code to do different things on the same project.


This topic is closed to new replies.

Advertisement