0

I am for the first time working on a larger C++ project. Now I need to share variables between two .cpp files. Namely I have variables that get edited in main.cpp and need to see their state in animals.cpp. What is the best way to share them between them? The way I was trying to do it was have another header file (variables.h) in which I had a class with static members that would then act as these variables. Sample code to explain:

variables.h:

class a {
public:
    static int x;
    static void init();
};

variables.cpp:

#include "variables.h"

void a::init() {
    x = 1;
}

animals.h:

void print_x();

animals.cpp:

#include "variables.h"

#include <iostream>

void print_x(){
    std::cout << a::x;
}

main.cpp:

#include "variables.h"
#include "animals.h"

int main(){
    a::init();
    a::x = 2;
    print_x();
}

When I compile together all .cpp files this however gives me the error:

objects.a(variables.cpp.obj):variables.cpp:(.rdata$.refptr._ZN1a1xE[.refptr._ZN1a1xE]+0x0): undefined reference to `a::x'

I do not necessarily want to achieve this sharing of variables with a class in a third header+cpp file but rather ask for the best way to do this.

I have looked at answers to similar problems where external was suggested. However I do not really want to create global variables and as I have understood it, external is used to share global variables.

Thanks for your help!

kamelfanger83
  • 97
  • 1
  • 7
  • 2
    Why do you want to wrap your variables in a class? If anything, it should be a namespace (then `extern` will work like with global ones). – HolyBlackCat Oct 20 '22 at 07:38
  • 1
    If `x` is really supposed to be a _constant_, then it should (probably) be declared `const` and have an initializer added directly to it instead of calling a function from main to set its value. Then you wouldn't need any of the `extern` or `class` stuff either. It would just work directly in the header file with the same syntax as any other variable. E.g. just `const int x = 1;` in the header. You can also call a function here which returns the value, but be careful if the calculation depends on other global variables. That is something that should generally be avoided. – user17732522 Oct 20 '22 at 07:42
  • 1
    Add "int a::x;" in animals.cpp – Equod Oct 20 '22 at 07:44
  • @HolyBlackCat Ok thanks I tried that and it worked. Do you think this is the best option? – kamelfanger83 Oct 20 '22 at 07:46
  • @user17732522 No it is not supposed to be a constant, the name constant is a but unfortunate I admit, I will change it. – kamelfanger83 Oct 20 '22 at 07:47
  • 2
    @kamelfanger83 If it is not a constant, then the usual advice is to try to not make it a global variable at all if possible. Define it as a local variable in `main` and then pass it to functions which need access to it by-reference or value as appropriate. – user17732522 Oct 20 '22 at 07:48
  • @kamelfanger83 I do. In languages without namespaces, classes are often abused for the same purpose, but there's usually no need to do it in C++. – HolyBlackCat Oct 20 '22 at 07:48
  • @user17732522 that was my fix before but it resulted in functions having more than 10 arguments which i found a little inelegant – kamelfanger83 Oct 20 '22 at 07:52
  • @HolyBlackCat Ok thanks a lot this already answers my questions in the comments... – kamelfanger83 Oct 20 '22 at 07:52
  • @kamelfanger83 If multiple of these arguments are related you can form a class collecting them if that logically makes sense, but not as static members. Instead have them be non-static members and still define the class variable in `main` and pass it on to functions. Also consider your design of the functions. Each function should perform one clearly-defined specific operation. Do they really need all these arguments for their specific purpose? Maybe some of these variables should be defined in other functions, where it logically makes more sense? – user17732522 Oct 20 '22 at 07:56
  • @user17732522 I thought about that option but it also seemed rather inelegant to me. It would be a possibility but I think HolyBlackCat's solution is more elegant. What do you think of his solution? – kamelfanger83 Oct 20 '22 at 08:02
  • @kamelfanger83 If you want to stay with global variables, I completely agree with their suggestion, but I also think that any kind of non-constant global variable makes code harder to reason about and I also think that variables should preferably be properly initialized in accordance with the RAII principle if possible, not by some additional `Init` call (that might also need a proper explicit destruction call I guess? The order of destructor calls for global variables will not match up with the order of such `Init` calls otherwise). – user17732522 Oct 20 '22 at 08:08
  • See also https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ri-global in the C++ core guidelines. – user17732522 Oct 20 '22 at 08:08
  • I also don't really see anything inelegant about forming proper classes. On the contrary, it allows separating the program into modules that can be reasoned about in isolation. The classes can also be simple with all members public and following the rule-of-zero if there is no class invariant that needs to be maintained. – user17732522 Oct 20 '22 at 08:11
  • I just thought a global namespace would be perfect in this scenario as there are never multiple different values of these variables (main.cpp sets them and all others use that one same set state). The init was only there due to the class, now with a namespace I made initialization in variables.cpp. The difference between the two solutions in mainly that with your solution I could have multiple members of the class with different states of the variables and with a namespace with global variables this is not possible. But I do not need it so this might be clearer, don't you think? – kamelfanger83 Oct 20 '22 at 08:17
  • or is there something generally wrong about global variables? – kamelfanger83 Oct 20 '22 at 08:20
  • @kamelfanger83 Well, I have no idea what your variables are actually used for. I am not saying there aren't exceptions to the general rule, but in most cases I think what I wrote applies. And even if your code currently cannot have multiple independent states for these variables, in most cases (again with exceptions) it makes at least in principle sense to consider multiple states, even if you don't need them just yet. – user17732522 Oct 20 '22 at 08:24
  • For example the C `srand`/`rand` interface has this problem (one of many). It uses global state which is completely fine in a single-threaded program, but then when you start to go multi-threaded you suddenly want to have individual random number generators for each thread for performance reasons, but the design doesn't allow implementing that due to the global state. Had the state be non-global from the start, it would be straight-forward to implement multiple instances once they are needed. – user17732522 Oct 20 '22 at 08:24
  • But all of that is opinion-based for the most part. I am sure there are also other opinions, but discussion of opinions is not really what stackoverflow is intended for. – user17732522 Oct 20 '22 at 08:26
  • Ok that makes sense. It is true that I don't know yet what I want to do with this in the future so I think I will switch to the class way now as it is not a lot to change anyways. Thanks! – kamelfanger83 Oct 20 '22 at 08:27
  • @user17732522 do you think a class or a struct would be more suitable for this application? – kamelfanger83 Oct 20 '22 at 08:34
  • @kamelfanger83 There is no difference between the two, both `struct` and `class` declare classes. Only difference is that a class defined with `class` keyword has `private` as default access specifier, while it is `public` when the `struct` keyword is used. – user17732522 Oct 20 '22 at 08:36

0 Answers0