How to manage file unique IDs in c++ - c++

c++ (not c++11)
Say i have 100 .cpp files in my project, which currently do some work.
All of those files currently include some globals.h file which i can edit easily.
I want each of those files to have its own instance of some object, and I also want that instance to have some unique ID of the file in which it is instantiated.
In addition, I want those objects to be created by a factory method, and I need the instances to have some way for a user to handle them - meaning they can't be anonymous.
In short - I need a way to generate unique IDs for all of the files in my project, i need the file to be able to access its own ID using the same name in all files, but also to be able to access the ID of a file externally in another "manager" file.
Here are options that don't work:
1.
Enum:
If I use an enum and give every file an enum ID, now I can't do this in globals.h:
static thePrivateInstanceInThisFile = theFactory.makeInstance(fileID);
because I need a different fileID in every file, and that was defined statically, and uniquely named using my enum.
2.
Class that counts its own instances
Define in globals.h:
class FileIDGiver{
private:
static int currentID;//initialize to 0 in cpp
int myID;
public:
FileIDGiver(){
myID = currentID++;
}
int getFileID(){
return myID;
}
}
static FileIDGiver theFileId;
static thePrivateInstanceInThisFile = theFactory.makeInstance(theFileId.getFileID());
This will give an ID to each static file instace which is unique to the file, but now it is not manageable externally to the file.
I thought about doing something like
globals.cpp
int file1ID;
int file2ID;
...
globals.h
extern file1ID;
extern file2ID;
...
file1.cpp
file1ID = theFileId.getFileID();
file2.cpp
file2ID = theFileId.getFileID();
...
and whenever a user needs to manage a file he would either use the file's ID variable, or create a new one in the above manner.
This would allow me to access each uniquely and automatically file ID externally.
The only problem I have with this is the line file1ID = theFileId.getFileID(); only executes in runtime, AFTER the line static thePrivateInstanceInThisFile = theFactory.makeInstance(theFileId.getFileID());.
which executes at compile time.
I can't figure out a good way to reverse this order, or maybe do a whole other mechanic.
Again - I need:
Automatically created file IDs
Unique file IDs (which are very very preferably numbers)
Usage of those IDs by the same variable name in all files (automatically, using a static variable definition in the globals.h file)
Ability to access a specific file ID manually by using another manually defined variable.
Please advise some good way to accomplish this
Thanks.

This sounds like a bad case of the static initialization order fiasco.
Here is a solution which uniquely assigns integer ids to each file, then generates a unique Instance by calling a factory function with the file's id, while ensuring that the Instance factory is initialized before its first use:
idgiver.h:
class IdGiver
{
int id;
public:
IdGiver() : id(0) {}
int getId() {return id++;}
};
IdGiver &getTheIdGiver();
idgiver.cpp:
#include "idgiver.h"
IdGiver &getTheIdGiver()
{
static IdGiver theIdGiver;
return theIdGiver;
}
factory.h:
class Instance
{
// ...
};
class Factory
{
// ...
public:
Factory() : {/*...*/}
Instance getInstance(int id) {/*...*/}
};
Factory &getTheFactory();
factory.cpp:
#include "factory.h"
Factory &getTheFactory()
{
static Factory theFactory;
return theFactory;
}
globals.h:
#include "idgiver.h"
#include "factory.h"
static int thisFileId = getTheIdGiver().getId();
static Instance thisFileInstance = getTheFactory().getInstance(thisFileId);

If you want to be able to access the static Instances of other files, then this cannot be done via an automatically generated id because the id generated for a file could change each time a new file is added, or every time it is compiled, or even on each execution. Therefore in this solution, each file manually defines its own persistent id similarly to example 1 in the question.
ids.h
enum FileId
{
File1, File2, File3
};
factory.h
#include "ids.h"
#include "instance.h"
class Factory
{
// ...
public:
Factory() {/*...*/}
Instance createInstance(FileId fileid) {/*...*/}
};
Factory &getTheFactory();
factory.cpp
#include "factory.h"
Factory &getTheFactory()
{
static Factory theFactory;
return theFactory;
}
idmanager.h
#include "ids.h"
#include "instance.h"
template<FileId id>
struct Manager
{
static Instance &getInstance(); // not defined
};
global.h
#include "idmanager.h"
#include "factory.h"
template <>
Instance &Manager<FILEID>::getInstance()
{
static Instance theInstance = getTheFactory().getInstance(FILEID);
return theInstance;
};
static Instance &getThisFileInstance()
{
return Manager<FILEID>::getInstance();
}
Usage is as follows: for each file requiring a static Instance object, place at the start
#define FILEID File1 // The FileId corresponding to this file
#include "global.h"
Then in any file,
The unique id is given by FILEID. (sorry it's a macro)
The static Instance of this file is obtained by getThisFileInstance().
The static Instance of any file is obtained by Manager<any_file_id>::getInstance().
This works by placing the implementation for an instantiation of the template Manager<FileId> in each file, each of which creates and returns that file's static Instance.
Advantages are persistence of ids, and zero run-time overhead: no need to dynamically assign ids, and the calls to Manager<file_id>::getInstance() are resolved at compile-time.
Also, the ids.h header can easily be generated by a script which scans the first line of each file for #define FILEID fileid, so the only maintenance left is remembering to write #define FILEID fileid.

You might modify your building procedure (e.g. your Makefile) to define some unique thing. E.g. you could compile your foo23.cpp file with something like (assuming GCC on some Linux system; adapt this to your compiler and OS and builder)
g++ -Wall -c -DBASENAME="$(basename foo23.cpp)" -DUNIQUEID=23
You could get the 23 for UNIQUEID using some shell script or whatever, e.g. an ad-hoc rule in your Makefile. Details depend upon your file naming conventions.
then use appropriately BASENAME and UNIQUEID in your C or C++ code (perhaps with dirty #if UNIQUEID==23 preprocessor tricks...).
So the idea is to generate the UNIQUEID in your build system and to pass it thru some preprocessor symbols. Details are OS, compiler, build-system specific.
You might also do some crude meta-programming by generating some C or C++ source or header file (perhaps using some awk script or some GPP or m4 preprocessing) in your building procedure.

Related

C++ Goodpractice for multiple derived class of the same base class

Goal: I am making a program which has 10 pattern styles, which are implemented as different classes that derive from a common pattern base class.
The main.cpp creates a pattern style depending on the style chosen by the user.
The question:
how can main know about all pattern styles without creating so many headers?
ex : these will be the files if separated.
baseclass.h
baseclass.cpp
derivedclass1.h
derivedclass1.cpp
derivedclass2.h
derivedclass2.cpp
derivedclass3.h
derivedclass3.cpp
derivedclass4.h
derivedclass4.cpp
main.cpp
inside main.cpp:
#include "derivedclass1.h"
#include "derivedclass2.h"
#include "derivedclass3.h"
#include "derivedclass4.h"
Isn't this too much?
Is there a way to just call 1 namespace which has a list of of all derived class like unity (deriving in monobehavior)? i know it uses c# but still..
=====thoughts=====
Maybe a foreach loop that calls all derived class of the same base class? or should I make a main_header.h which #include all the style and include that from main.cpp?
Include header files, not .cpp files.
If you still need to include .cpp files for some reason: Don't (but rename it to _impl.h etc. for templates)
Instead include header files
#include "derivedclass1.h"
#include "derivedclass2.h"
#include "derivedclass3.h"
#include "derivedclass4.h"
If that seems too much: it is not a sin to put alike classes in the same file (albeit it should be a rare case):
Then include that:
#include "allderivedclasses.h"
Your main concern seems to be "how can main know about all pattern styles?".
The answer is a factory function takes a pattern style name (and maybe some arguments) and produces the correct pattern style object. Each pattern style implementation is responsible for registering itself to the factory, such that main can remain oblivious.
For a more elaborate explanation of this technique, see https://dev.to/fenbf/factory-with-self-registering-types--35h1
In the code below, the PatternStyle class exposes two static functions:
register_pattern: Each sub-class can register its name and a static constructor function at startup time into a std::map pattern_styles.
create: Looks up the name in the std::map and invokes the constructor, if any.
pattern_style.h
class PatternStyle {
...
public:
using Constructor = std::function<std::unique_ptr<PatternStyle>()>;
static std::unique_ptr<PatternStyle> create(const std::string& name);
static bool register_pattern(const std::string& name, Constructor ctor);
};
pattern_style.cpp
static std::map<std::string, PatternStyle::Constructor> pattern_styles;
std::unique_ptr<PatternStyle> PatternStyle::create(const std::string& name) {
auto it = pattern_styles.find(name);
if (it == pattern_styles.cend())
return {};
else
return it->second();
}
bool PatternStyle::register_pattern(const std::string& name, Constructor ctor) {
pattern_styles[name] = ctor;
return true;
}
This allows a subclass to register itself like so:
wavy_pattern_style.h
class WavyPatternStyle : public PatternStyle {};
wavy_pattern_style.cpp
static bool registration = PatternStyle::register_pattern("wavy", std::make_unique<WavyPatternStyle>);
Note: this use of make_unique requires C++14. If you only have C++11, you will need to make a dedicated constructor function (or use a lambda).
With this construct, it is a simple matter of defining pattern styles in separate files and including them in your build system, which solves your secondary concern.

Use one globe variable in all cpp files.If one class of the cpp file changed the value,I want to access it from another class cpp file

I want to Use one globe variable in all cpp files.If one class of the cpp file changed the value,I want to access it from another class cpp file,which is the value that least modified by any other cpp class file.
str.h - global variable file
#ifndef MY_PROG
#define MY_PROG
extern char * myname;
#endif
class1.cpp
#include "str.h"
char * myname;
class class1
{
class1(){}
~class1(){}
void Setname1(char *name) { myname = name }
};
class2.cpp
#include "str.h"
char * myname;
class class2
{
class2(){}
~class2(){}
void setName(char *name) { myname = name }
};
class3.cpp
#include "str.h"
class class3
{
class3(){}
~class3(){}
char *GetData()
{
return myname;
}
};
main.cpp
#include "str.h"
int main()
{
class1 c1;
class2 c2;
c1.Setname1("XXXX");
c2.setname("YYYY");
class3 c3;
cout << c3.GetData;
}
when I execute the program, I need to get Last modified value that is "YYYY" .I am new to cpp, And also please tell me whether I used the extern keyword correctly.If not , please provide me the right procedure.
The essential in your problem is understanding the difference between declarations and definitions of variables (and types, functions etc. objects in C/C++)
extern char * myname; is a declaration. It makes myname visible to other pieces of code in the same file so that they can reference it.
char * myname; is a definition. Not only it makes myname visible, it also instructs the compiler to allocate space for it and to make its address known.
You can have as many declarations of the same variable in your code as you want, as long as they do not contradict each other. No so with definitions. If you define a thing two times, it would need to be assigned two addresses, and then how can other object files "understand" which address to use when referencing it? The same goes with the space allocated — what to do with the extra piece of it allocated? This is the error that you see.
To make the code work, have only one and exactly one definition of myname in exactly one file. It must not be a header file because it gets copied into multiple source files thus creating multiple definitions. It can be any other C++ file though.
In the rest of the files (or in a single header included in all of them) have multiple declarations of myname if it is referenced in a particular file. If not, you can omit it for a particular unit.
All this being said, it is considered to be a VERY BAD PRACTICE to communicate data between compilation units through global mutable shared variables. They make code a nightmare to debug and understand and impossible to parallelize. Nobody ever thinking getting money for the code they write should use them. Instead, the best approach would be to pass a mutable object as one of methods/functions argument. Details actually depend on the rest of your application.

C++03 linker "already defined symbol" doesn't appear on intermediate file

I am having a problem with a large project on visual studio 2005 on which I have run out of ideas.
I can't even put a working code snippet because I don't know what's related, but I will try:
I needed to make each .cpp file in my project have its own ID number, and create an instance of an object (which is globally accessible) that knows that ID.
I followed the help on the accepted answer on this thread How to manage file unique IDs in c++
and made it work in a sandbox environment.
Adding files, giving them a unique #define FILEID (FileId::ID_FileName)
and then accessing their instance works fine on the sandbox.
Now comes the trouble -
I pasted the code that makes files know their IDS to the main project, and compiled.
So far so good.
Now, I added to one of the existing .cpp files in the project:
#include "ids.h"
#define FILEID File1 // The FileId corresponding to this file
#include "global.h"
Still compiles, links, all good.
Adding these lines to a (any) second .cpp file in the project
now gives link error:
in which:
name1: 1st file I added the lines to (alphabeticcaly)
name2: other unrelated filename (which can also be the 2nd file I added the lines to, but may as well be just some other file)
The error
in name2.obj : error LNK2005: "public static class Instance & __cdecl Manager<3>::getInstance(void)" (?getInstance#$Manager#$02##SAAAVInstance##XZ) already defined in name1.obj
Some times the error is only in the second file, and sometimes (between consecutive builds without changes) the error appears on every .cpp file in the folder.
Looking in the intermediate file (the preprocessor output) on the files to which I added the lines shows exactly one appearance of the
template <>
Instance &Manager<FILEID>::getInstance()
{
static Instance theInstance = getTheFactory().getInstance(FILEID);
return theInstance;
};
with the correct FileId::ID_FileName, which is a different name than that of the other file.
Still, the linker thinks the same FileId is used in more than one file.
On unrelated files (which also give the exact same error), there is no appearance of getInstance() at all. Apparently, there shouldn't be a reason for the linker to shout there.
I checked, and no .cpp files include each other somewhere in the project.
I am completely out of ideas as to what could cause this
and would appreciate any help.
EDIT 1
ids.h
enum FileId{
ID_file1ID=3,//just to see a non zero number in the debugger, which I do
ID_file2ID,
//and so on
FileIdSize
}
EDIT 2
When these errors start, the compiler starts to behave extremely unexpectedly.
Adding the line sdfsdfgasaedfahjk to any file STILL COMPILES AND PASSES.
it clearly states the file name to which the line has been added to compiles.
It clearly states it links to it.
It passes.
I now can't trust the compiler.
No idea what's going on.
You have 2 cpp files defining the FILEID to the same value 3.
As for a MCVE:
ids.h:
#pragma once
#define File1 3
#define File2 3 //<--same value on purpose
global.h
struct Instance
{
};
struct Factory
{
Instance getInstance(int FileID) { return Instance(); }
};
template <int ID>
struct Manager
{
Factory factory;
Instance& getInstance();
Factory& getTheFactory() { return factory; }
};
template <>
Instance& Manager<FILEID>::getInstance()
{
static Instance theInstance = getTheFactory().getInstance(FILEID);
return theInstance;
};
name1.cpp
#include "ids.h"
#define FILEID File1 // The FileId corresponding to this file
#include "global.h"
name2.cpp
#include "ids.h"
#define FILEID File2 // The FileId corresponding to this file
#include "global.h"
As this compiles there is a special implementation for Manager<3>::getInstance(void) created for both name1.cpp and name2.cpp.
You can't use the same value for FILEID in 2 different compilation units.
EDIT: Check values while compiling
Requires the preprocessor definition __BASE_FILE__="%(Filename)%(Extension)"
(Configuration Properties -> C/C++ -> Preprocessor -> Preprocessor Definitions)
template <>
Instance& Manager<FILEID>::getInstance()
{
#define _STR(x) #x
#define STR(x) _STR(x)
#define CHECK_ID() __pragma(message("Initializing \"Instance& Manager<FILEID>::getInstance()\" with FILEID="STR(FILEID)" in "STR(__BASE_FILE__)))
CHECK_ID()
static Instance theInstance = getTheFactory().getInstance(FILEID);
return theInstance;
};
Example-Output:
1>------Build started : Project : Test_Call, Configuration : Debug Win32------
1> name1.cpp
1> Initializing "Instance& Manager<FILEID>::getInstance()" with FILEID = FileId::ID_file1ID in "name1.cpp"
1> name2.cpp
1> Initializing "Instance& Manager<FILEID>::getInstance()" with FILEID = FileId::ID_file2ID in "name2.cpp"
1> Test_Call.vcxproj-><Project>\Debug\Test_Call.exe
== == == == == Build: 1 succeeded, 0 failed, 0 up - to - date, 0 skipped == == == == ==
EDIT: Using FileId values as template parameter (MSVE)
id.h
#pragma once
enum FileId {
ID_file1ID = 3,//just to see a non zero number in the debugger, which I do
ID_file2ID,
//and so on
FileIdSize
};
global.h
#pragma once
#include "ids.h"
struct Instance
{
};
struct Factory
{
Instance getInstance(int FileID) { return Instance(); }
};
template <FileId ID>
struct Manager
{
static const FileId manager_id = ID;
static Factory& getTheFactory() { return m_factory; }
static Instance& getInstance()
{
static Instance theInstance = getTheFactory().getInstance(manager_id);
return theInstance;
}
private:
static Factory m_factory;
};
global.cpp
#include "global.h"
Factory Manager<FileId::ID_file1ID>::m_factory;
Factory Manager<FileId::ID_file2ID>::m_factory;
name1.cpp
#include "global.h"
void test1()
{
Instance& a = Manager<FileId::ID_file1ID>::getInstance();
}
name2.cpp
#include "global.h"
void test2()
{
Instance& a = Manager<FileId::ID_file2ID>::getInstance();
}
test.cpp
#include <iostream>
#include "global.h"
using namespace std;
int main(int argc, char** argv)
{
Instance& a = Manager<FileId::ID_file1ID>::getInstance();
Instance& b = Manager<FileId::ID_file2ID>::getInstance();
Instance& c = Manager<FileId::ID_file1ID>::getInstance();
Instance* aptr = &a;
Instance* bptr = &b;
Instance* cptr = &c;
printf("aptr==bptr -> %s\n", (aptr == bptr) ? "true" : "false"); //->false
printf("aptr==cptr -> %s\n", (aptr == cptr) ? "true" : "false"); //->true (both use the instance from ID_file1ID
printf("bptr==cptr -> %s\n", (bptr == cptr) ? "true" : "false"); //->false
}
This is not an answer, but may prove useful in finding out what is wrong.
The following code is essentially the same as the original answer, but with all complexity stripped away at the expensive of needing boilerplate code in various places.
idmanager.h
struct Instance {/*...*/};
Instance &getFile1Instance();
Instance &getFile2Instance();
// etc...
idmanager.cpp
Instance &getFile1Instance()
{
static Instance file1instance;
return file1instance;
}
Instance &getFile2Instance()
{
static Instance file2instance;
return file2instance;
}
// etc...
In each file, place at the start
#include "idmanager.h"
and you can get the static Instance of any file in the obvious way.
This is as simple as it can possibly get, so copying it into your project simply can't cause a problem.
If the above example worked, then try making it slightly closer to the original answer: move the definitions of the getFileXInstance functions into the files themselves, and delete idmanager.cpp.
idmanager.h
struct Instance {/*...*/};
Instance &getFile1Instance();
Instance &getFile2Instance();
// etc...
file1.cpp
#include "idmanager.h"
Instance &getFile1Instance()
{
static Instance file1instance;
return file1instance;
}
file2.cpp
// etc...
Clearly this just moves the code around between different .obj files, so should still work.
Now replace each getFileXInstance function with a struct with a single static member function, getInstance, as follows:
idmanager.h
struct Instance {/*...*/};
struct Manager1
{
static Instance &getInstance(); // defined in file1.cpp
};
struct Manager2
{
static Instance &getInstance(); // defined in file2.cpp
};
// etc...
file1.cpp
#include "idmanager.h"
Instance &Manager1::getInstance()
{
static Instance file1instance;
return file1instance;
}
file2.cpp
// etc...
The previous step allows us to reduce the amount of boilerplate code using templates:
idmanager.h
struct Instance {/*...*/};
template <int id>
struct Manager
{
static Instance &getInstance(); // each instantiation has its definition in a different cpp file
};
file1.cpp
#include "idmanager.h"
template <>
Instance &Manager<1>::getInstance()
{
static Instance file1instance;
return file1instance;
}
This is where linker errors are most likely to start appearing again, if they do at all.
More repetition can also be removed by putting the common code in a shared header globals.h, and communicating the preprocessor constant FILEID to it.
idmanager.h
struct Instance {/*...*/};
template <int id>
struct Manager
{
static Instance &getInstance(); // each instantiation has its definition in a different cpp file
};
file1.cpp
#include "idmanager.h"
#define FILEID 1
#include "globals.h"
globals.h
template <>
Instance &Manager<FILEID>::getInstance()
{
static Instance theInstance;
return theInstance;
}
This last example is now the same as the original answer, with a few differences (no factories, no enums, no getThisFileInstance()) which are irrelevant to the linker errors. Therefore (assuming the first example worked) you can identify which change broke the program, and that should help to diagnose the real problem.
(note: although your error is exactly that which would appear if multiple files shared the same id, from the comments I assume this is not the case.)

Proper implementation of global configuration

My goal is to have global constants in a C++ game I'm working on (to represent some graphics info and the like). My current implementation is to toss them all in a .h and include them everywhere. This works, except that every time I change a setting, the entire code base must be recompiled.
So, my next idea was to toss them in some configuration txt file and parse them in, that way no code is actually changed when settings change. The parser was simple enough, and I could put the values into the constants, but because the parser was a code block, the constants were no longer global.
Is there a good way to solve this? Perhaps some way to make them global despite being in a block or some way to avoid recompiling everything when changing settings?
The way I used solve this is to put the variables in a separate global namespace, which is in a header file named something like config.h, then include that file everywhere.
// In config.h
#ifndef CONFIG_H
#define CONFIG_H
namespace config
{
extern int some_config_int;
extern std::string some_config_string;
bool load_config_file();
}
#endif
Then in a source file, you define the variable and also set them to a default value. This source file also have the code to load the variables from your configuration file.
// In config.cpp
namespace config
{
int some_config_int = 123;
std::string some_config_string = "foo";
}
bool config::load_config_file()
{
// Code to load and set the configuration variables
}
Now in every source file you need the configuration variables, include config.h and access them like config::some_config_int.
However, there is no "proper" way of solving this, all ways that work are proper in my eyes.
Another way to do this would be to create a singleton class.
#include <fstream>
#include <map>
#include <string>
class ConfigStore
{
public:
static ConfigStore& get()
{
static ConfigStore instance;
return instance;
}
void parseFile(std::ifstream& inStream);
template<typename _T>
_T getValue(std::string key);
private:
ConfigStore(){};
ConfigStore(const ConfigStore&);
ConfigStore& operator=(const ConfigStore&);
std::map<std::string,std::string> storedConfig;
};
Here the configuration is saved in a map, meaning as long as parseFile can read the file and getValue can parse the type there is no need to recompile the config class if you add new keys.
Usage:
std::ifstream input("somefile.txt");
ConfigStore::get().parseFile(input);
std::cout<<ConfigStore::get().getValue<std::string>(std::string("thing"))<<std::endl;
What about creating functions that return your constants that you can specify in a .cxx file? For example:
// foo.h
const int BAR();
// foo.cxx
const int BAR() {
return 10;
};
put only the declarations in head file and put the definitions in a cpp file. then you change the definitions in cpp file will not cause all code recompiled

Python-like storage of static data in C++?

When I have test data objects, or other static data objects such as SQL strings, I like to keep them out of application code. In Python, this is done with a separate source file and an import as shown below. How is static data organized and used C++?
(file: testdata.py)
x = Foo() (an object)
x.name = "Bar"
x.number = 123
..
(file: test.py)
import testdata.py
testObject1 = testdata.x
..
There are lots of possibilities. This also depends on what you're trying to do (e.g. do you want the values to be editable after compiling or only before compiling?
As for the later, you could use a separate translation unit.
In some header file:
extern const char *name;
extern const int number;
In one separate source file:
const char *name = "Bar";
const int number = 123;
However, you could do the class/struct approach, too:
Header file:
struct Foo {
const char *name;
int number;
};
extern const Foo Bar;
Source file:
const Foo Bar = {"Bar", 123};
The most flexible choice for connection strings etc. would be to create a settings file (with a format of your own choosing) and a class that can read the file, since this allows you to change the settings without recompiling the program.
For static data that you want to include in the code, you can use static class members:
// file.h
class Foo {
public:
static string getBar();
private:
static string bar;
};
// file.cpp
Foo::bar = "qwe";
static Foo::getBar() {
return bar;
}
You can put them in another file as well; extern data declarations in an header file, and the definitions in a .cpp file, like this:
// Foo.h
extern Foo afoo;
extern int anint;
// Foo.cpp
Foo afoo;
int anint = 0;
Then you can #include "Foo.h" wherever you want to use afoo and anint.
There are many ways to store data out of the source code.
For example, configuration files are stored in XML for example, though (fortunately) JSON has been gaining some grounds (not as much as I'd like to), and there are also custom formats.
Another example, for translation you might use gettext and store strings in .po files, and add new languages without recompiling.
It all works the same way, in the end:
you define a format for the file
you create a reader (and perhaps writer) procedure that converts between the file format and the in-memory format
you find a way to pass the file path to the program: fixed name in current directory, fixed name in home directory, command line argument, environment variable, etc...
If you wish to do it as you did in Python, you could also compile the resources into their own DLL and pull them at runtime while loading the symbols, changing the resources would only require re-compiling this one DLL.