MarcusAseth

Again Inclusion question

Recommended Posts

Hi guys, I would like to have the header below:

Paths.h

#pragma once
#include <string>

namespace Paths {
	
	namespace Images {
		static std::string CHECKER = "Graphics/Checker.png";
	}

	namespace Fonts {
		static std::string BUBBLEBATH = "Fonts/BUBBLEBATH.ttf";
	}

}

Included all around the place so that I can do stuff like Paths::Images::CHECKER to easily load the proper file or to request it again by passing that to a map as a key.

Though this I believe create a copy of the static string for every compilation unit, am I correct? Meaning I include it in 10 cpp and get 10 copies of each string.  How would I do this the proper way?

Share this post


Link to post
Share on other sites

Change the "static" with "const"? Or don't use static and const if you still want to be able to change these strings.

If static is used outside of a function or class, it ensures the variable/function can only be used by code in that specific file (translation unit to be precise), and nowhere else. const variables are prevented from changing.

Furthermore, use lower case characters for globals. Most programmers use full upper case for macros. You coud use something like g_checker and g_bubblebath to explicitly indicate these variables are global (though scoped to some namespace). Alternatively, you can consider to use macros for these file names if you intend to add hundreds of them to your code. This will reduce your memory usage.

Edited by matt77hias

Share this post


Link to post
Share on other sites

The exact answer depends on compiler and options you use. Duplicates can be eliminated by compiler/linker, especially if LTO (Link Time Optimization) can be used.

See: "Overhead of static constants when included into many translation units?"

Apparently C++17 introduces inline variables.

inline const std::string my_string = "MyString";

 

Edited by matt77hias

Share this post


Link to post
Share on other sites

Ok I think I've figured it out myself, extern to the rescue :D

Paths.h

#pragma once
#include <string>

namespace Paths {
	
	namespace Images {
		extern std::string CHECKER;
	}

	namespace Fonts {
		extern std::string BUBBLEBATH;
	}

}

Paths.cpp

#include "Paths.h"

std::string Paths::Images::CHECKER = "Graphics/Checker.png";
std::string Paths::Fonts::BUBBLEBATH = "Fonts/BUBBLEBATH.ttf";

 

Share this post


Link to post
Share on other sites
header file

namespace Paths {
	
	namespace Images {
		extern std::string CHECKER;
	}

	namespace Fonts {
		extern std::string BUBBLEBATH;
	}

}
source file 
#include <Paths.h>

namespace Paths {
	
	namespace Images {
		std::string CHECKER = "Graphics/Checker.png";
	}

	namespace Fonts {
		std::string BUBBLEBATH = "Fonts/BUBBLEBATH.ttf";
	}

}

 

Share this post


Link to post
Share on other sites
1 minute ago, MarcusAseth said:

@AxeGuywithanAxe I've beaten you by few seconds  But thanks

Also, any major differences in the way I did it above and in the way you did it? Or is a style choice? 

Yeah, i saw that.. haha... not a difference, just style choice and laziness (I just copied and pasted your original comment and added extern and removed static , etc)

Share this post


Link to post
Share on other sites
7 minutes ago, jpetrie said:

Generally I create free functions to provide well-known constant values that cannot just be constexprs.

@jpetrie can you show me an example of one of this free function?

I would like to understand where my global variables should end up living, at which point during executing are they initialized, how this free functions get to them and how all the other files get to this free functions ^_^'

 

Edited by MarcusAseth

Share this post


Link to post
Share on other sites

Simplest form is to create them inside main(), and pass them around as needed. You could make a class for them, so related variables are close together.

It's not a real global, but you have much more control over them, and you have to make explicit that some code relies on them, which will one day help in refactoring code (ie avoid the case that you got all parameters, and then find out code also has a huge number of dependencies on globals you completely forgot about.)

 

Share this post


Link to post
Share on other sites
32 minutes ago, Alberth said:

Simplest form is to create them inside main(), and pass them around as needed.

Not a big fan of that, some object are just too widely used like App for requesting textures to it and Game to allow entity to interact with other entities in the game or the global paths to be used for loading and requesting textures to App (in the code above), I wouldn't want to pass this stuff down constructors and inheritance chains just to end up storing a pointer to each one of them inside of every object or something. And this in case I have understood what you are saying correctly, for me is always hard without a concrete code example to backup an explanation. :\

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
2 hours ago, MarcusAseth said:

Not a big fan of that, some object are just too widely used

That fact, that they are "widely used," is indicative of a design failure. In a non-trivially-sized project, there are very few things that actually "need" to be accessed from anywhere.

Passing dependencies around like Alberth is suggesting is the better way that making a ton of secret globals. It makes dependencies clear, allows you to enforce contracts in your interfaces, and so on. Globals are impossible to control effectively and impossible to reason about.

 

3 hours ago, MarcusAseth said:

can you show me an example of one of this free function?

 

"SomeThing getSomeThing() { return SomeThing(value, of, some, thing); }" if SomeThing has value semantics or "SomeThing& getSomeThing() { static SomeThing result(value, of, some, thing); return result; }" if it's more complex, et cetera.

 

Share this post


Link to post
Share on other sites

@jpetrie sorry, still not clear ^_^' I guess I am missing the bigger structure here, where everything is placed in comparison to everything else, like in which header is this function compared to who calls it, where are the things result is builted with taken from and how you reach them and so on...

SomeThing& getSomeThing()
{ 
	static SomeThing result(value, of, some, thing);
	return result; 
}

so this above is the function.

So from what I am understanding, in practice would look something like this, and it will live in the Global.h:

Global.h

#include "App.h"
#include "SDL2\SDL.h" 

struct AppInfo {
		App* App;
		GameMode* Game;
		SDL_Window* Window;
		SDL_Renderer* Renderer;
		Uint32 Width, Height;
	};

AppInfo& GetAppInfo()
{ 
	static AppInfo result{MyApp.GetApp(), MyApp.GetGame(), MyApp.GetWindow(), MyApp.GetRenderer(), MyApp.GetWinWidth(), MyApp.GetWinHeight()};
	return result; 
}

But the problem is, when is that static initialized?! If it is when I run the program, then App wouldn't be initialized yet... is that the case? If so, then I still don't get it how to do it...

Furthermore, where is this "App MyApp"  global object created and initialized, I mean where is living? :\

The example is still too abstract for my brain, the problem is I can't see the entire structure pattern here... :\

Edited by MarcusAseth

Share this post


Link to post
Share on other sites

Why not cut using "variables" completly for string constants and use some inline accessor function to grab it?

namespace MyGame
{
   namespace Constants
   {
      inline const char* Files() { return "./Assets"; }
      inline const char* OpenGl() { return "./Lib/OpenGl.dll"; }
   }
}

...
  
void* gl_lib = (void*)LoadLibraryA(MyGame::Constants::OpenGl());

Any clever compiler (and I think even MSVC should solve this) optimizes the inline call away for a pure string constant access without any considerable overhead

Share this post


Link to post
Share on other sites

@Shaarigan if I understand correctly what you're saying, that's seems messy to me because then I have to compose in place the name of the thing I want to load like string(MyGame::Constants::Files()) + "Graphics/Checker.png" and I have to remember the name of stuff all over the place, much easier to just call Paths::Images::CHECKER and let it do the right thing.

Edited by MarcusAseth

Share this post


Link to post
Share on other sites
1 hour ago, MarcusAseth said:

string(MyGame::Constants::Files()) + "Graphics/Checker.png"

Let it return a const std::string instead of a const char *. Then you can already slightly reduce it to

MyGame::Constants::Files() + "Graphics/Checker.png", which looks pretty reasonable (assuming you do not perform this a million times per second).

Share this post


Link to post
Share on other sites
Just now, matt77hias said:

Let it return a const std::string instead of a const char *. Then you can already slightly reduce it to

MyGame::Constants::Files() + "Graphics/Checker.png", which looks pretty reasonable (assuming you do not perform this a million times per second).

Looks still pretty bad to me...All I am doing right now to load an image is this:


//Load Textures
LoadTexture(Paths::Images::CHECKER);

and to get it from another file,

Checker::Checker(float x, float y):
	Tile(x,y,Paths::Images::CHECKER)
{
}

The fact I write it in 2 different places increase chances for mistakes if I where to manually write it.

This way, I simply cannot make a mistake, and if I mispell it inside the Paths.h, then that error get caught by the LoadTexture.

Share this post


Link to post
Share on other sites
4 minutes ago, MarcusAseth said:

The fact I write it in 2 different places increase chances for mistakes if I where to manually write it.

Fair point, but what if you want to change the directory of your assets? How many changes does that inccur at the moment?

You could also encapsulate your resources in some object which provides a method for retrieving the filename, file name and file path. And then you can pass a reference to the resource object around. This avoids the need for duplicating the concatenation everywhere.

Edited by matt77hias

Share this post


Link to post
Share on other sites
2 minutes ago, matt77hias said:

Fair point, but what if you want to change the directory of your assets? How many changes does that inccur at the moment?

Exactly 1.  Hold ALT pressed inside of visual studio, do a rectangle box selection, write the new directory name. And this is why I have them all lined up inside a single file. The way you guy suggest, request go inside of each and every file 1 by 1 and fix it.

Share this post


Link to post
Share on other sites
8 minutes ago, MarcusAseth said:

Exactly 1.  Hold ALT pressed inside of visual studio, do a rectangle box selection, write the new directory name. And this is why I have them all lined up inside a single file. The way you guy suggest, request go inside of each and every file 1 by 1 and fix it.

Ok but in Software Engineering a change is a change independent of some solution (.sln) wide global search-and-replace (which does multiple changes for you). The latter is some handy utility to help you do it efficiently, but it is no remedy for the disease (referring to implicit dependencies).

Edited by matt77hias

Share this post


Link to post
Share on other sites

Why are you loading textures manually in one place, but also passing the path in a different (seemingly unrelated) place? Your original post says it's to be able to request the texture again, but why would you ever want that?

I would have expected the tile to only hold a pointer to its texture. Upon creation, load the texture (if need be, have some caching system or similar to prevent multiple loads of the same texture) and store a pointer to the texture in each tile/object.

Share this post


Link to post
Share on other sites

I wouldnt trust C++11/14/17 support anyways but thats another topic.

To quote Hodgman, game IO should be a lot more limited than generic IO and so your game should anyways NOT use full paths to read Assets from disk. What I do in my framework is to set a fixed working directory and address my assets relative to that using "./" if necessary  (to address packages)

I do not fully understand objections because however you change paths/files, you do it in one single file used in many other files anyways so changing a string constant or function return should be regardless. However, global string constants have disadvantages as mentioned in any above post due to initialization order and local unit copies where inline header functions are optimized away mostly and directly accessed without code impact. Another option is for sure using macro constants so you wont have any access overhead.

It is ok for small projects and debugging code to go for direct file access but in medium or larger ones there should at least be some kind of package for disk aligning and IO performance reasons

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now