|
Trouble not using global variables/functions |
Felix-The-Ghost
Member #9,729
April 2008
|
Ugh. I'm trying to rewrite my program to use classes/structs instead of globals. main.cpp ... #include "gamedata.h" ... gamedata game; ... set_close_button_callback(game.quit_game); ... gamedata.h #ifndef GAMEDATA #define GAMEDATA struct gamedata { bool quit; void quit_game(); void check_for_quit(); }; #endif gamedata.cpp void gamedata::gamedata() { quit = false; } void gamedata::quit_game() { quit = true; } void gamedata::check_for_quit() { //I don't even know what I'd do here } In main I get error: argument of type `void (gamedata::)()' does not match `void (*)()' I really want to be able to write my program the right way |
jmasterx
Member #11,410
October 2009
|
You cannot set a callback to class functions, instead, you need to make polymorphic listeners or use a message system. //your problem is: set_close_button_callback(game.quit_game); The basic idea is game.quit_game() actually points to a function that takes in an invisible pointer to your class instance (this) and so C++ won't know to use your 'game' instance because of how the language works. To do what you want, Google 'signal slot pattern' . std::function from c++0x can bind the this pointer so that could do it too. A quick fix is to make quit_game() a static function. The disadvantage is that the function will be like a C one so it won't have access to your class's instance variables. You would also access the static function like a scope: set_close_button_callback(game::quit_game); If you are just starting to learn Allegro, move to Allegro 5. It is better, and it supports proper events which will allow you to make your game object oriented more easily than Allegro 4. Agui GUI API -> https://github.com/jmasterx/Agui |
Felix-The-Ghost
Member #9,729
April 2008
|
Last five times I checked, I have maybe the one modern-ish enough computer* that should but can't run Allegro 5 programs. I've been using Allegro 4 for my projects for 2-3 years. I'm just not too good at technical parts. Like horrible. But I got logic itself down enough to think it in my head. I could use a global (is that what you mean by static) function, but that would require a global variable to check at the end of each loop. That's what I used to do, check if quit is true, then return from main. Is there a small A5 program I can test on this computer? Compiled since the official release of A5? *Its like a 2003 Dell running Win XP. Integrated graphics card. I don't know whether or not I feel like using A5 will shun users of older computers, but right now I'm starting to not care. It has been almost eight years :x The project I'm rewriting was originally all in one source file, a little over 700 lines of code. A good part of it is comments and whitespace, but still more than I should be using one source for. |
Edgar Reynaldo
Major Reynaldo
May 2007
|
There's no reason not to use a global variable here. Don't be paranoid about globals. Sometimes they're the right tool for the job. bool quit = false; void quit_game() {quit = true;} // in main set_close_button_callback(quit_game); And yes, you could use a static quit variable and a static quit_game method. It would be effectively the same, except it would be affected by it's class access level. Felix-The-Ghost said: Is there a small A5 program I can test on this computer? Compiled since the official release of A5? Yes, download the examples for A5 from the allegro files page. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Oscar Giner
Member #2,207
April 2002
|
You can't avoid having a global here, since there's no version of set_close_button_callback that takes a callback with an extra parameter, like install_param_int does (in this case you could avoid the global, using a static member function and passing the "this" pointer manually). -- |
Felix-The-Ghost
Member #9,729
April 2008
|
I'll use a global; in what situations is a global okay to use? Also, just tried out some Allegro 5 examples and the demos. It's really cool (works) but before some games hadn't worked with me... Non of the audio examples work with me nor the "acodec" examples. In some of the examples I can choose from a memory or video bitmap. Memory is really slow while video is fast. Is this to demonstrate a point? Allegro 4 used memory blending right? Some other examples run a bit in their log windows but close quickly (I can't tell what some of them do just by trying the executable out ) The code for A5 is a lot different though |
Edgar Reynaldo
Major Reynaldo
May 2007
|
In my opinion, globals are okay whenever you need a single shared instance of an object. Felix-The-Ghost said: Non of the audio examples work with me nor the "acodec" examples. They probably take an audio file name as an argument. Try running the examples from the command line. Then they should tell you if they need command line parameters. The manual and the wiki are the best places to learn Allegro 5. Ask questions here if you get stuck. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Matthew Leverton
Supreme Loser
January 1999
|
Removing globals does not guarantee a good design. If you put all of your globals into a struct and expose that struct, then you've accomplished nothing. The most important thing is to try to keep implementation details as private as possible. If your computer is old, then I'd not bother with A5 until you at least upgrade the video card (if it's a desktop machine...). |
Felix-The-Ghost
Member #9,729
April 2008
|
Most of the examples worked with my computer. Edit: I'm having trouble with external classes. Keeps saying I never defined it. In this case, it says player is never defined. I'm trying to change the instance of the class snake, player's direction via a global function. main.cpp 1#include <allegro.h>
2#include <vector>
3
4#include "gamedata.h"
5#include "snake.h"
6#include "segment.h"
7
8using namespace std;
9
10...
11
12/** GLOBAL VARIABLES **/
13
14bool quit = false;
15
16/** GLOBAL FUNCTION DECLARATIONS **/
17
18void quit_program();
19void check_keys();
20
21...
22
23snake player(game.buffer->w/2, game.buffer->h/2); 24
25...
26
27int main(void)
28{
29
30...
31
32/** MAIN LOOP **/
33
34while(!quit)
35{
36check_keys(); 37
38game.draw(&player);
39}
40
41return 0;
42
43} END_OF_MAIN()
snake.h 1#ifndef SNAKE
2#define SNAKE
3
4#include "segment.h"
5
6#include <vector>
7using namespace std;
8
9class snake
10{
11 public:
12 int head_x;
13 int head_y;
14 int head_dir;
15
16 vector <segment> body;
17
18 snake(int x, int y);
19};
20
21#endif
snake.cpp #include "snake.h" snake::snake(int x, int y) { head_x = x; head_y = y; head_dir = -1; } global_functions.cpp 1#include <allegro.h>
2#include "snake.h"
3
4extern snake player; 5
6extern bool quit;
7
8void quit_program()
9{
10 quit = true;
11}
12
13void check_keys()
14{
15 if (key[KEY_ESC])
16 quit = true;
17
18 if (key[KEY_LEFT] && player.head_dir != 2)
19 {player.head_dir = 0; return;}
20
21 if (key[KEY_UP] && player.head_dir != 3)
22 {player.head_dir = 1; return;}
23
24 if (key[KEY_RIGHT] && player.head_dir != 0)
25 {player.head_dir = 2; return;}
26
27 if (key[KEY_DOWN] && player.head_dir != 1)
28 {player.head_dir = 3; return;}
29}
The extern worked on the global quit variable, but even in another incident I could not make the snake class external. I bypassed it earlier by passing the address of player, but I'd rather not have any function parameters for this (Or really the other function where I was drawing it) I could have the snake class have its own code for checking keys, and in the check_keys function call that, but then each instance of the class would have the same controls (should I desire more instances). Is there a better (correct) way to do this? |
Edgar Reynaldo
Major Reynaldo
May 2007
|
'extern' declarations go in headers, never in source files. All they do is say that 'this variable is available to you'. They still have to be defined somewhere else though. Felix-The-Ghost said: I could have the snake class have its own code for checking keys, and in the check_keys function call that, but then each instance of the class would have the same controls (should I desire more instances). Is there a better (correct) way to do this? Add 4 int members to your snake class that hold the value of the keys you want to check and then check if (key[MOVE_LEFT]) {...}. Look up what an initialization list is, as you don't use one. Give them default values in the initialization list. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
gnolam
Member #2,030
March 2002
|
And never use namespaces in headers. -- |
Felix-The-Ghost
Member #9,729
April 2008
|
gnolam said: And never use namespaces in headers. Noted, but is it unnecessary or destructive? Or both? I'll not do it, but I don't know why other than you said not to right now. Edgar Reynaldo said: 'extern' declarations go in headers, never in source files. All they do is say that 'this variable is available to you'. They still have to be defined somewhere else though. Oh okay. I thought it'd function the same in the source file since it worked for the external Boolean variable quit. I'll try it in a header. I guess I'll need to make a globals declaration header. I'm hoping the creation of player in the main.cpp counts as a definition. Quote: Add 4 int members to your snake class that hold the value of the keys you want to check and then check if (key[MOVE_LEFT]) {...}. Look up what an initialization list is, as you don't use one. Okay, I'm trying to look up "initialization lists", though I'm not sure what my goal is. So far it looks like just a constructor, but I'll keep reading into it I guess. Quote: Give them default values in the initialization list. I'm not sure if this is supposed to be plainer to me, but I assume I'll know what you mean when I read more on these lists. Right now it looks ambiguous. I've never succeeded in storing the keys flags in variables. I've asked for help in the past but I guess my wording was inaccurate cause I never got a satisfactory answer. I mean I don't know know how to store what keys is needed as a variable (I'm assuming you mean so I can initialize it with a constructor taking those keys as parameters, but I could be inferring wrongly) |
Edgar Reynaldo
Major Reynaldo
May 2007
|
If you 'use' a namespace in a header, then every file that includes that header is automatically using that namespace as well, and it can lead to naming conflicts that can be avoided by not doing it. Initialization lists are just that - write out your constructor prototype, then follow it by a class Test { int i; char c; float f; public : Test() : i(0), c('X'), f(3.14f) { // ... constructor body as normal } };
Felix-The-Ghost said: I've never succeeded in storing the keys flags in variables. I've asked for help in the past but I guess my wording was inaccurate cause I never got a satisfactory answer. I mean I don't know know how to store what keys is needed as a variable (I'm assuming you mean so I can initialize it with a constructor taking those keys as parameters, but I could be inferring wrongly)
1int my_special_key = KEY_ENTER;
2
3// ...
4if (key[my_special_key]) {/*...*/}
5
6bool my_special_key_pressed = key[my_special_key];
7if (my_special_key_pressed) {/*...*/}
8
9class Test {
10 int key1;
11 int key2;
12
13public :
14 Test() : key1(KEY_1) , key2(KEY_2) {}
15 Test(int key_one , int key_two) :
16 key1(key_one),
17 key2(key_two)
18 {}
19};
My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Felix-The-Ghost
Member #9,729
April 2008
|
Edgar Reynaldo said: int my_special_key = KEY_ENTER; // ... if (key[my_special_key]) {/*...*/} Thanks, I haven't tested that, but I assume that works. I guess I didn't know they were integers. Seriously, thanks. I didn't know how to let my user configure keys. Does this work for shift/ctrl/alt flag, too? Quote: Initialization lists are just that - write out your constructor prototype, then follow it by a semicolon, and then a comma separated list of constructor calls for each member of your class :
Hmm. I didn't know you could do that in constructors. I guess you'd have to use constants, right? I mean to take parameters I'd need a constructor with parameters, yes (like parameters for keys to use, for example) I could always assign them later. |
Edgar Reynaldo
Major Reynaldo
May 2007
|
You don't need to use constants in the constructor calls of the initialization lists : class Test { int key1; int key2; public : Test() : key1(KEY_1) , key2(KEY_2) {} Test(int key_one , int key_two) : key1(key_one), key2(key_two) {} }; See how key1 is constructed using the value of the parameter key_one? Any valid symbol or built in data type will do (as long as there is a constructor that takes that kind of object). My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Felix-The-Ghost
Member #9,729
April 2008
|
Yeah I typed that before I saw the overloaded constructor I just saw this Test() : key1(KEY_1) , key2(KEY_2) {} Hey, do these count as the function declarations and definitions? Since the only code is there? Edit: My gamedata struct works, but the snake class isn't. Is there some different syntax to this or did I do something stupid? gamedata.h global_variables.h #ifndef GLOBAL_VARIABLES #define GLOBAL_VARIABLES #include "snake.h" #include "gamedata.h" bool quit; gamedata game; snake.h 1#ifndef SNAKE
2#define SNAKE
3
4#include "global_variables.h"
5#include "segment.h"
6#include "gamedata.h"
7
8#include <vector>
9
10class snake
11{
12 public:
13 int head_x;
14 int head_y;
15 int head_dir;
16
17 std::vector <segment> body;
18
19 snake():
20 head_x(game.buffer->w/2),
21 head_y(game.buffer->h/2),
22 head_dir(-1)
23 {}
24};
25
26#endif
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
First, you have a circular include cycle - global_variables.h includes snake.h which includes global_variables.h and so on... Fix it by not including global_variables.h in snake.h. If you need access to variables in global_variables.h in snake.h, then you need to remove all references to the snake header in the global variables header. Second, never define objects in a header, only declare them. Example : global_variables.h #include "snake.h" #include "gamedata.h" extern snake player; extern gamedata game; global_variables.cpp #include "global_variables.h" snake player; gamedata game; I don't see why you need so many globals though. Can't you just pass a snake& or gamedata& to your functions instead? Also, you can eliminate your player object from the globals by making it a member of your gamedata class. You will only ever have one snake right? So just add it to your gamedata class. Then all you need to make everything available is : gamedata.h class gamedata {/*...*/}; extern gamedata game; gamedata.cpp #include "gamedata.h" gamedata game; And then when you need access to your game, you just include the gamedata header. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Felix-The-Ghost
Member #9,729
April 2008
|
Ahh, somehow I forgot -- I had even done it before, declaring the struct and class instances inside of the header that defines them. I moved the game and player declarations to gamedata.h and snake.h. Yeah, I already have a function that is passed the address of player because of the same problem (not knowing player was [supposed to be] declared) I thought it'd be interesting to see if the code could handle another snake (just for testing, to see how easy it'd be with "correct" code) forgetting to put "extern" was stupid of me. I don't know how I missed that (although I didn't realize that would define them) gamedata.h still isn't liking this line, though It doesn't know snake is a type, still (I thought including the snake.h would fix that) I only planned on one more global at most, but I probably will add the snake instance to gamedata. I had already pondered on it. I'm not sure if I should just declare the bool quit in main.cpp or not (getting rid of the global_variables.h) since only main is using quit I can tell circular includes is gonna be a problem for me. I thought the #ifndef GLOBAL_VARIABLES #define GLOBAL_VARIABLES #endif made it okay. Hmm. I was originally just including the headers in other headers using classes/structs from them (eg including snake.h in gamedata.h but not working as I planned) I think I did everything you said... Here let me paste what I got: global_variables.h (will probably get rid of this) #ifndef GLOBAL_VARIABLES #define GLOBAL_VARIABLES #include "gamedata.h" extern bool quit; #endif gamedata.h snake.h 1#ifndef SNAKE
2#define SNAKE
3
4#include "segment.h"
5#include "gamedata.h"
6
7#include <vector>
8
9class snake
10{
11 public:
12 int head_x;
13 int head_y;
14 int head_dir;
15
16 std::vector <segment> body;
17
18 snake():
19 head_x(game.buffer->w/2),
20 head_y(game.buffer->h/2),
21 head_dir(-1)
22 {}
23};
24
25extern snake player;
26
27#endif
I appreciate your patience in helping me with this. This is really not coming easy to me, and I struggled with learning it in the past and gave up and coded everything in one file |
Edgar Reynaldo
Major Reynaldo
May 2007
|
Felix-The-Ghost said: It doesn't know snake is a type, still (I thought including the snake.h would fix that) There must be something wrong with your snake.h header then, because as long as the class is declared properly it should work. Post the latest snake.h. Felix-The-Ghost said: I can tell circular includes is gonna be a problem for me. I thought the #ifndef GLOBAL_VARIABLES made it okay. Hmm. The header guards prevent the same header from being included twice in the same file, but circular inclusion is still wrong and can lead to other problems. If a function needs info from more than one header, then it should be a separate header as well. You've got another circular include going on - gamedata.h includes snake.h which includes gamedata.h. Both classes shouldn't need to know about each other. Fix it by adding class snake; in gamedata.h before you declare class gamedata. That is known as a forward declaration, and allows you to use pointers and references to whatever you forward declare that way. However, you are doing something a little dangerous - you are using a global object (game) inside your snake constructor. If snake is defined before game, then you will be referencing an object that hasn't been constructed yet. You should never rely on global constructor order, since it is undefined across compilation units. I'm pretty sure in a single file it goes in the order they are defined, but you still shouldn't count on it. To fix it, just use a setter method in your snake class where you initialize its position. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Felix-The-Ghost
Member #9,729
April 2008
|
Sweet. Something clicked for me -- I had been including whole headers (in other headers) when a certain struct/class wasn't recognized. So I do forward declarations instead, and in source files (cpp) there, I include the headers. I might be wrong with this, but I compiled with no warnings (I had to modify pretty much every file, and I eliminated global_variables.h) main.cpp 1#include <allegro.h>
2#include <vector>
3
4#include "gamedata.h"
5#include "snake.h"
6#include "segment.h"
7
8using namespace std;
9
10/** GLOBAL VARIABLES **/
11
12bool quit = false;
13gamedata game;
14snake player;
15
16...
17
18game.init();
19
20...
gamedata.h gamedata.cpp #include <allegro.h> #include "gamedata.h" #include "snake.h" ... } snake.h 1#ifndef SNAKE
2#define SNAKE
3
4#include "segment.h"
5#include "gamedata.h"
6
7#include <vector>
8
9class snake
10{
11 public:
12 int head_x;
13 int head_y;
14 int head_dir;
15
16 std::vector <segment> body;
17
18 snake():
19 head_dir(-1)
20 {}
21
22 void position(int x, int y);
23};
24
25extern snake player;
26
27#endif
snake.cpp #include "snake.h" void snake::position(int x, int y) { head_x = x; head_y = y; } global_functions.cpp 1#include <allegro.h>
2
3#include "snake.h"
4
5extern snake player;
6extern bool quit;
7
8void quit_program()
9{
10 quit = true;
11}
12
13void check_keys()
14{
15 if (key[KEY_ESC])
16 quit = true;
17
18 if (key[KEY_LEFT] && player.head_dir != 2)
19 {player.head_dir = 0; return;}
20
21 if (key[KEY_UP] && player.head_dir != 3)
22 {player.head_dir = 1; return;}
23
24 if (key[KEY_RIGHT] && player.head_dir != 0)
25 {player.head_dir = 2; return;}
26
27 if (key[KEY_DOWN] && player.head_dir != 1)
28 {player.head_dir = 3; return;}
29}
It compiles fine, but are there some bad include practices in there I should know about? I really appreciate your help so far, I got a lot of this down (finally), but I'm not sure this is 100% "correct". I probably don't need to include <vector> in snake.h, do I? |
Edgar Reynaldo
Major Reynaldo
May 2007
|
Felix-The-Ghost said: global_functions.cpp #include <allegro.h> #include "snake.h" extern snake player; extern bool quit;
Don't use extern in source files - it's akin to cheating, and besides, including snake.h took care of that for you already. snake.h no longer needs to include gamedata.h because you aren't using 'game' references in it anymore. Felix-The-Ghost said: I probably don't need to include <vector> in snake.h, do I? I should use a forward declaration, but what is a vector? A struct/class? Hmm. Googling. the std:: thing takes care of it I think. I know that is supposed to mean standard namespace or something, but I don't really know why that works. snake.h needs to know about the vector class template, so you do need to include <vector>. Forward declarations don't work when you want to define objects of that class or use them by value. About namespaces : header.h namespace MYLIB { class MYCLASS {/*...*/}; } main.cpp #include "header.h" using namespace MYLIB;// this brings every symbol declared in MYLIB into global scope using MYLIB::MYCLASS;// this brings only the MYCLASS symbol into scope int main(int argc , char** argv) { MYLIB::MYCLASS myclass;// fully qualified, helps avoid name collisions MYCLASS myclass;// using the short name to refer to it return 0; }
My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Felix-The-Ghost
Member #9,729
April 2008
|
Edgar Reynaldo said: Don't use extern in source files - it's akin to cheating, and besides, including snake.h took care of that for you already. snake.h no longer needs to include gamedata.h because you aren't using 'game' references in it anymore. Oops, missed those. I must've hurried through it. I'll have to read up on namespaces, I guess, cause I still don't really get them (as far as usefulness of custom namespaces) edit: I fixed those includes and globals (I brought back global_variables.h for quit for global_functions.cpp I was removing including custom headers in other headers, but I got a problem when I don't include segment.h in snake.h, even with a forward declaration of segment snake.h 1#ifndef SNAKE
2#define SNAKE
3
4#include <vector>
5
6class segment; 7
8class snake
9{
10 public:
11 int head_x;
12 int head_y;
13 int head_dir;
14
15 std::vector <segment> body; 16
17 snake():
18 head_dir(-1)
19 {}
20
21 void position(int x, int y);
22};
23
24extern snake player;
25
26#endif
segment.h #ifndef SEGMENT #define SEGMENT class segment { public: int x; int y; }; #endif
It says error: invalid use of undefined type `struct segment' Actually, currently until I make a segment.cpp, nothing includes segment.h -- is it still declared, then? |
Edgar Reynaldo
Major Reynaldo
May 2007
|
Felix-The-Ghost said: I was removing including custom headers in other headers, but I got a problem when I don't include segment.h in snake.h, even with a forward declaration of segment
Edgar said: Forward declarations don't work when you want to define objects of that class or use them by value. You're using a vector of segment objects, and so it needs to know what they are, not just that they exist, so forward declarations won't work in this case. Forward declarations only work when you're using pointers or references, and you still need their full declaration when you go to use them (in a source file). Felix-The-Ghost said:
It says error: invalid use of undefined type `struct segment' To save having two error messages instead of one? Don't know, but like I said, it needs to know the full declaration of the segment class. Felix-The-Ghost said: Actually, currently until I make a segment.cpp, nothing includes segment.h -- is it still declared, then? If you never include it, it was never declared. Felix-The-Ghost said: I was removing including custom headers in other headers, There's nothing wrong with including headers in headers, if they need to know about what is declared in them. Felix-The-Ghost said: I'll have to read up on namespaces, I guess, cause I still don't really get them (as far as usefulness of custom namespaces) Namespaces help avoid naming collisions when using multiple libraries. Imagine A and D are from two different libraries - if class C were declared in both libraries without being inside a namespace, then you would have problems. namespace A { class C {int y;}; } namespace D { class C {int x;}; } int main(int argc , char** argv) { A::C ac; D::C dc; return 0; }
My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Felix-The-Ghost
Member #9,729
April 2008
|
|