0

so what I want is to have a global.h files that contain variables/functions for other classes/functions to use. For instance, say I have three header files: global.h, local1.h, and local2.h. Here, local1.h and local2.h are going to use variables from global.h. However, if I include global.h in both local1.h and local2.h, it would result in a multiple definition error as expected. I understand we can use the keyword "extern" but I heard its bad practice so I'm trying to avoid that.

I've tried to come along with this problem by using classes. Basically, I have a base class that contains variables that are all static. This way any class that would use those variables may just inherit from the base class. (in my case I'm fine with it as most of my programs are made out of classes). Below is the code,

Base.h

class base{
    static const int SCREEN_SIZE = 1000;
};

class1.h

#include "base.h"
class class1: public base{
    void printSize(){
        cout << SCREEN_SIZE << endl;
    }
};

class2.h

#include "base.h"
class class2: public base{
    int getSize(){
        return SCREEN_SIZE;
    }
};

But I'm not sure if this is the best way to do it, any suggestions?

Here's my actual code:

#ifndef GAME_CORE_H
#define GAME_CORE_H

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>

namespace Colors{
const SDL_Color RED = {255, 0, 0, 255};
const SDL_Color GREEN = {0, 255, 0, 255};
const SDL_Color BLUE = {0, 0, 255, 255};
const SDL_Color YELLOW = {255, 255, 0, 255};
const SDL_Color PURPLE = {128, 0, 128, 0};
const SDL_Color ORANGE = {255, 68, 0, 255};
const SDL_Color WHITE = {255, 255, 255, 255};
const SDL_Color BLACK = {0, 0, 0, 255};
};

namespace GAME_CORE{
SDL_Window* gWindow = NULL;
SDL_Renderer* gRenderer = NULL;
SDL_Surface* gWindowSurface = NULL;
TTF_Font* defaultFont = NULL;

const int SCREEN_INIT_WIDTH = 1280;
const int SCREEN_INIT_HEIGHT = 720;
const int TILESIZE = 32;

void CORE_Init();
};


#endif // GAME_CORE_H

so the error msg showed multiple definitions of gWindow, gRenderer, gWidowSurface, and defaultFont

Rhathin
  • 1,176
  • 2
  • 14
  • 20
John Liu
  • 5
  • 2
  • 1
    Use proper *Header-Guards* and you eliminate that problem.... [How do I use extern to share variables between source files?](https://stackoverflow.com/questions/1433204/how-do-i-use-extern-to-share-variables-between-source-files/1433387?r=SearchResults&s=1|129.0531#1433387) and [Header guards in C++ and C](https://stackoverflow.com/questions/4767068/header-guards-in-c-and-c?r=SearchResults&s=3|106.2853) – David C. Rankin Dec 27 '19 at 06:57
  • 1
    Does this answer your question? [How do I use extern to share variables between source files?](https://stackoverflow.com/questions/1433204/how-do-i-use-extern-to-share-variables-between-source-files) – David C. Rankin Dec 27 '19 at 07:02
  • Yes, I did that but it gave me multiple definition errors. I have a header file named GAME_CORE.h and I included in main.cpp and in another header file I included in there as well. – John Liu Dec 27 '19 at 07:04
  • You don't show any header-guards in your question. When you conditionally include a header only if it has not already been included in the project -- you eliminate the potential for multiple inclusion. You must read really fast -- Leffler's answer in the first link I provided is quite thorough and detailed on the subject (a bit of a novel really....) Please provide [A Minimal, Complete, and Verifiable Example (MCVE)](http://stackoverflow.com/help/mcve), I'm a bit unclear what you are asking based on your last comment. – David C. Rankin Dec 27 '19 at 07:06
  • so now I have my code posted, when I include this header file in another file twice, it would give me multiple definition error. I know I can use extern here, but is that the only solution? – John Liu Dec 27 '19 at 07:27
  • 2
    *"... showed multiple definitions of gWindow, gRenderer, gWidowSurface, and defaultFont"* -- you cannot assign namespace variables in header files [multiple definition of namespace variable, C++ compilation](https://stackoverflow.com/questions/8971824/multiple-definition-of-namespace-variable-c-compilation) – David C. Rankin Dec 27 '19 at 07:29
  • 1
    consider using `enum` for global integer-type constants – M.M Dec 27 '19 at 07:40
  • Thanks David!!! That worked, this is my first time on stack overflow so idk how I can give you a like or something. if you show me how to give you a BIG LIKE, i'll give you the BIG LIKE. – John Liu Dec 27 '19 at 07:41
  • *we can use the keyword `extern` but I heard its bad practice*: You are misunderstanding the point: you should avoid global variables. And by doing that, you automatically do not need `extern`. – j6t Dec 27 '19 at 08:23
  • @JohnLiu - no worries, the goal is that you find the information you need to continue building your knowledge as a programmer. Choose the best answer below that solves you issue -- and as you become an older member here - remember to give back to the new folks on their first time here `:)` – David C. Rankin Dec 27 '19 at 08:41
  • The point about global variables is that you easily lose control about where and when they get modified, and bugs resulting from that are often just a question of time. Looking at your namespace GameCore, it looks as if you could easily convert it to a class (CORE_init() getting converted into constructor then). Then you'd create that class e. g. inside main function. Operations on that GUI elements would all occur inside *member functions*. That way, you'd have eliminated the globals entirely... – Aconcagua Dec 27 '19 at 08:47

3 Answers3

0

I suggest .h files should be use for prototypes and not actually declaration of any types or functions except:

inline variables / functions static variables / functions const variables using int = Reg; typedef Reg int;

I prefer you implement it in two options

var.h
class var{
   public:
    var()=default;

  private:
     int _list {};

    operator +(int); //prototypes

};



var.cpp
//Then u can implement the declaration like
   #include"var.h"
    class var{
    //Implement it here

   };

Or you can do as the standard library does that declare full classes in files with no extension.

Here is a C program that can generate those files

int main (void){
   FILE* ptr = fopen ("vector","w+");
}
JoelJediz
  • 11
  • 3
0

Use a header guard.

For example

Base.h

#ifndef base_h
#define base_h
class base{
    static const int SCREEN_SIZE = 1000;
};
#endif
alok
  • 1,218
  • 1
  • 12
  • 29
0

At very first, if you include a header multiple times, you always get multiple definitions if you allow the code to get repeated by multiple inclusion. Using extern keyword to resolve the issue is not the way to go, you use that for other purposes. Apart from, it wouldn't work for classes!

The right way to do so is using include guards in the header file:

#ifndef SOME_GUARD_NAME
#define SOME_GUARD_NAME
// your code in between here
#endif

If you encounter#pragma once: That does the same job, but is non-standard. I don't know a recent compiler not supporting it, but if you want to stay fully portable, still add the include guards as fall-back!

extern keyword is used to tell the compiler that a variable exists, but is defined elsewhere. If you have globals accessed from different compilation units, then you need to define the global variable in exacly one of, while the others get aware of via a declaration (using extern keyword).

I do not see much of a problem having constants being defined as global variables (delcared extern):

// header file:

namespace Colors
{
extern const SDL_Color RED;
extern const SDL_Color GREEN;
extern const SDL_Color BLUE;
extern const SDL_Color YELLOW;
extern const SDL_Color PURPLE;
extern const SDL_Color ORANGE;
extern const SDL_Color WHITE;
extern const SDL_Color BLACK;
};

// source file:

const SDL_Color Colors::RED = {255, 0, 0, 255};
const SDL_Color Colors::GREEN = {0, 255, 0, 255};
const SDL_Color Colors::BLUE = {0, 0, 255, 255};
const SDL_Color Colors::YELLOW = {255, 255, 0, 255};
const SDL_Color Colors::PURPLE = {128, 0, 128, 0};
const SDL_Color Colors::ORANGE = {255, 68, 0, 255};
const SDL_Color Colors::WHITE = {255, 255, 255, 255};
const SDL_Color Colors::BLACK = {0, 0, 0, 255};

They are constants, so the trouble you can get into by global variables is avoided as these are not modifiable anyway (even concurrent read in multi-threaded environments is not a problem).

It is pretty easy, though, to lose overview over where and when mutable global variables are modified (it's the global variables being bad practice, not the extern keyword...). And you can add a big factor to that in multi-threaded environments. Bugs can easily arise from. That's why you usually avoid global variables.

Having an eye over your namespace GAME_CORE, then it looks pretty much as if it was a good idea to convert that into a class:

class GameCore
{
    // depends on how they are created, maybe std::unique_ptr is more appropriate?
    // you might avoid necessity of a destructor
    SDL_Window* m_window;
    SDL_Renderer* m_renderer;
    SDL_Surface* m_windowSurface;
    TTF_Font* m_defaultFont; // maybe rather a static constant?

public:
    // constants can easily be public:
    unsigned int const SCREEN_INIT_WIDTH = 1280;
    unsigned int const SCREEN_INIT_HEIGHT = 720;
    unsigned int const TILESIZE = 32;

    //void CORE_Init(); // replace this one with constructor:
    GameCore();

    //to properly clean up objects you created inside:
    ~GameCore(); // destructor 
};

You'd now convert all other functions accessing those GUI elements into member functions as well, some public, some private, just depending on the needs. Then you'd create a GUI like this:

int main()
{
    GameCore gc; // or possibly with parametrised: GameCore gc(7, 10, 12);

    // and use it from within main, e. g. simply:
    return gc.run();
}

If you still insist on global variables being accessed from different sources, then you do rely on extern keyword (but still use the include guards!)

// header file:

#ifndef SOME_GUARD_NAME
#define SOME_GUARD_NAME
namespace GAME_CORE
{
extern SDL_Window* gWindow; // no assignment!
// ...
}
#endif

// source file:

// you still need a definition in EXACTLY ONE compilation unit:
SDL_Window* GAME_CORE gWindow = nullptr;

Side note: You should prefer C++ keywords (nullptr) over old (obsolete) C macros (NULL) – unless explicitly writing code for old environments not supporting at least C++11.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59