Splitting up project in .h and .cpp

Started by
15 comments, last by c4c0d3m0n 16 years ago
Important Rule: Never explicitly call a destructor, unless you know what you are doing. People posting in "For Beginners" do not fall under this category. It is extremely rare thing to have to do, even if you happen to be an experienced programmer.

Other than that you have a few errors in your code. Here is my take on it. These are only suggestions.

#include "game.h"int main( int argc, char* argv[] ) {    Game pong;    if( pong.mainMenu() ) {       // you declared Game::play to return an integer       // either use the return value, or don't return a value       return pong.play();    }    return 0;}

#ifndef GAME_H#define GAME_H// The preferred way to include SDL is like this// This allows games to be source portable among systems where SDL is not// in a subdirectory called SDL. Instead, set your additional include path// to include /path/to/your/include/SDL#include "SDL.h"#include "graphics.h"#include "ball.h"struct Gamestate{public:    Gamestate(const Ball &ball)    :        ball(ball)    {    }    void process(double t, double dt);    // anticipating future need    void draw(Gfx &gfx);private:    Ball ball;};const int SCREEN_W = 600;const int SCREEN_H = 400;class Game{public:    // don't write empty constructors or destructors    Game()    :        gfx(SCREEN_W,SCREEN_H)    {    }    // ~Game();    bool mainMenu();    int play();private:    // these can be local to play    // SDL_Event event;    // Gamestate* current_state;    // Gamestate* previous_state;    // at the moment, this could be made a local too.    // it is kept as a member because your mainMenu function is likely to    // make use of it.    Gfx gfx;};// these can be made part of the ball class// extern Ball newBall( int x, int y, float velocity, float angle, float rad );// extern void updateBall( Ball* b, double dt );#endif

#include "game.h"#include "timer.h"// these are included in "game.h"// include "ball.h"// include "SDL.h"void Gamestate::process(double t, double dt ){    // Move ball    ball.update( dt );    // Todo: Rest of processing, eg. collision detection}bool Game::mainMenu(){    return true; // Placeholder for a future main menu}int Game::play(){    Timer timer;    const float DELTA = 0.01;    // prefer values to pointers    // previous_state = new Gamestate;    // current_state = new Gamestate;    // I have removed the "previous" state, as it doesn't appear     // to be used in any meaningful way    Gamestate current(Ball( 100, 100, 0.1, 0.0, 5.0 ));    bool loop = true;    SDL_Event event;    while( loop ) {        timer.fillAccu();        while( timer.pinchAccu( DELTA ) ) {            current.process( timer.getTimePassed(), DELTA );        }        while( SDL_PollEvent( &event ) ) {            if( event.type == SDL_QUIT )              loop = false;        }        current.draw( gfx );    }    return 0;}

#ifndef GFX_H#define GFX_H#include "SDL.h"class Gfx{public:    // by taking the screen dimensions as arguments, this class    // is easier to re-use in future projects    Gfx(int width, int height);    ~Gfx();    // do "init" work in constructor    // note: you used the return value to signal success or failure    // however, your game class never checked the result!    // int init();    // the graphics objects shouldn't really care what they are drawing    // they shouldn't access the game state    // instead, other objects should tell it what to draw    // and where    // void draw( Gamestate* state );private:    SDL_Surface* screen;    void drawPixel( int x, int y );};#endif

#include "graphics.h"// include "game.h"// C++ standard headers have no extension#include <cmath>// we include this so we can use// the standard C++ exception types#include <stdexcept>// exceptions are the easiest way to signal such errors// this is a fatal error usually.Gfx::Gfx(int width, int height){    screen = 0;    if( SDL_Init( SDL_INIT_VIDEO ) < 0 )    {        throw std::runtime_error(SDL_GetError());    }    SDL_WM_SetCaption( "PongSDL", NULL );    screen = SDL_SetVideoMode( width, height, 32, SDL_SWSURFACE );    if( !screen )    {        // we need to ensure SDL_Quit is called on all code paths        // if the graphics object is successfully constructed, its        // constructor will call it. If this error occurs though        // we must call it here        SDL_Quit();        throw std::runtime_error(SDL_GetError());    }}Gfx::~Gfx(){    // SDL says you shouldn't free the screen surface    // SDL_Quit does this for you    SDL_Quit();}void Gfx::drawPixel( int x, int y ){    SDL_Rect rect;    rect.x = x;    rect.y = y;    rect.w = 1;    rect.h = 1;    SDL_FillRect( screen, &rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) );}

#ifndef BALL_H#define BALL_H// balls are now smart objects// they can set themselves up, and can update themselvesstruct Ball{public:    Ball(float x, float y, float velocity, float angle, float radius );    void update(float dt);private:    float x;    float y;    float velx;    float vely;    float radius;};#endif

#include "ball.h"#include <cmath>// use helper functions to increase code clarityfloat to_radians(float degrees){     return degrees / 180.0f * 3.14f;}float cos_degrees(float angle){     return cos(to_radians(angle));}float sin_degrees(float angle){     return sin(to_radians(angle));}Ball::Ball( float x, float y, float velocity, float angle, float radius ):   x(x),   y(y),   velx(cos_degrees(angle)),   vely(sin_degrees(angle)),   radius(radius){}void Ball::update( float dt ){    x += dt * velx;    y += dt * vely;}
Advertisement
Thanks so much for the detailed reply! I used almost all your tips. I changed my structs into classes, because I somehow feel wrong defining constructors and functions to structs. I'm running into a problem though.

I feel I shouldn't make my Gfx object local, because it is rather big, and it would improve loading times coming from the main menu to the game if it only needs to be declared once. (I know, we're talking about Pong, but it could be a very big fancy game hypothetically)

I removed the init() function and put it in the constructor as said:

class Gfx{public:    Gfx();    Gfx( int screen_w, int screen_h );    ~Gfx();    void drawPixel( int x, int y );    void drawCircle( int x, int y, int r );private:    SDL_Surface* screen;};Gfx::Gfx(){    screen = 0;}Gfx::Gfx( int screen_w, int screen_h ){    if( SDL_Init( SDL_INIT_VIDEO ) < 0 )      throw std::runtime_error( SDL_GetError() );    SDL_WM_SetCaption( "PongSDL", NULL );    screen = SDL_SetVideoMode( screen_w, screen_h, 32, SDL_SWSURFACE );    if( !screen ) {        SDL_Quit();        throw std::runtime_error( SDL_GetError() );    }}Gfx::~Gfx(){    SDL_Quit();}


The problem is now that my gfx object in my Game class gets constructed with the default constructor automatically when I declare it, and I can't seem to call on the fancy constructor anymore in the Game's constructor.

Game::Game(){    gfx.Gfx( GAME_SCREEN_W, GAME_SCREEN_H );}

error: invalid use of `class Gfx'

Game::Game(){    gfx = Gfx( GAME_SCREEN_W, GAME_SCREEN_H );}

No compilation errors, the program runs according to my taskmanager, but after a short flash of a window, the window dissappears as if the video initiation fails.


How can I now use the fancy constructor for my non-local gfx object?
I actually showed the code required to do that in my example. You need to use an initialiser list. See my version of your "Game" class for an example.

However, you appear to have added a default constructor to your Gfx object. This constructor however does not initialise the object properly (because you cannot use a default-constructed Gfx object, it has no screen). You must either change your default constructor to provide reasonable defaults for the screen dimensions, or (my suggestion) remove it.

Another issue that I did not mention is the Rule Of Three. The rule of three is a C++ rule and it states that if you have any of the following, a destructor, copy constructor or operator= overload, then you need all three.

However, there is a "get out" clause. Instead of implementing the copy constructor and assignment operator, we can disable copying for our object. We can do this by declaring both functions, but not implementing them. Also, we place them private. This prevents the compiler from automatically generating them for us (which is wrong, objects with pointers tend not to be copyable by default). If you ever accidentally try to copy a Gfx object, the compiler will spit out an error.

{public:    Gfx( int screen_w, int screen_h );    ~Gfx();    void drawPixel( int x, int y );    void drawCircle( int x, int y, int r );private:    // These are deliberately left private and unimplemented.    // Do not copy Gfx instances!    Gfx(const Gfx &);    Gfx &operator=(const Gfx &);     SDL_Surface* screen;};
class Gfx{public:    //Gfx();    Gfx( int screen_w, int screen_h );    ~Gfx();    // Draws a single white pixel    void drawPixel( int x, int y );    // Draws a circle with radius r and M(x;y)    void drawCircle( int x, int y, int r );private:    // These are deliberately left private and unimplemented.    Gfx( const Gfx & );    Gfx &operator=( const Gfx & );    SDL_Surface* screen;};

Game::Game(): gfx(Gfx( GAME_SCREEN_W, GAME_SCREEN_H )){}
graphics.h:31: error: `Gfx::Gfx(const Gfx&)' is private
game.cpp:33: error: within this context
graphics.h:31: error: `Gfx::Gfx(const Gfx&)' is private
game.cpp:33: error: within this context


For now, I've commented those unimplemented lines out, that fixed the problem.
Take a closer look at what the code you're copying was, and note the difference.
You still don't understand. That error is the very error I was talking about when I said that the compiler will warn you when you try and copy a Gfx instance.

Again, I will ask you to look carefully at the Game class in my post. Can you see how it differs from what you are trying to compile?
Ah, now I see, I feel stupid for not noticing that myself... Thanks a lot though! I think I finally understand the idea behind proper splitting. I can finish my game now :)

This topic is closed to new replies.

Advertisement