0

I have 2 header files and 1 source file to work with. Respectively: Utility.h, Game.h and main.cpp. I have declared extern variables in Utility.h and am trying to define them in main.cpp. This gives me an undefined reference error, which I don't understand. I use the variable AFTER I give it a value, so that should be fine?

Utility.h:

#pragma once

#include <string>
#include <fstream>
#include <SDL.h>

namespace Utility {
    extern std::string BIN_PATH;
    extern std::string ROOT_PATH;
    extern std::string EXE_PATH;

    // Omitted rest of namespace.
}

main.cpp

#include "Game.h"

int main(int argc, char * argv[]) {
    // Get the necessary paths
    std::string path = SDL_GetBasePath();

    #ifdef _WIN32
        // TODO check if working on different windows systems
        //  exePath = path;
        //  binPath = path.substr(0, path.find_last_of('\\'));
        //  rootPath = binPath.substr(0, binPath.find_last_of('\\'));
    #elif __LINUX__
        Utility::BIN_PATH = path.substr(0, path.find_last_of('/'));
        Utility::ROOT_PATH = Utility::BIN_PATH.substr(0, binPath.find_last_of('/'));
        // TODO check if working on different linux systems
    #endif

    std::cout << "BinPath: " + Utility::BIN_PATH << std::endl;
    std::cout << "RootPath: " + Utility::ROOT_PATH << std::endl;

    // Omitted rest of source.
}

I am including Utility.h in Game.h, and Game.h in main.cpp. Shouldn't that put the extern definitions above my main.cpp source when linking?

nepp95
  • 131
  • 1
  • 13
  • Your variables don't have definitions, only declarations. It's like declaring `void foo(int);` in a header and then trying to call `foo(42)` without defining the function anywhere. – melpomene Mar 09 '18 at 20:55
  • Maybe that "undefined reference error" comes with a more explicit message, or maybe a line number? – Renardo Mar 09 '18 at 20:55
  • 1
    Possible duplicate of [What is an undefined reference/unresolved external symbol error and how do I fix it?](https://stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix) – melpomene Mar 09 '18 at 20:55
  • 2
    @melpomene we need a less generic canonical, the one specifically for ODR violations. – SergeyA Mar 09 '18 at 20:58

2 Answers2

3

To simplify (rather complicated) rules a bit, each variable (as well as other entities) must be defined once and only once in the whole program. It can be declared multiple times. It is important to understand what is a declaration and what is a definition.

extern std::string var; // in namespace scope

Is a declaration of string variable var. var is not yet defined. You need to define it somewhere else - only once - by using

std::string var; // in namespace scope!

In your code, you do not define the variable in function main - instead, you are assigning the value to it. But the variable needs to be defined.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Aaah, this explains a lot... So if I read this correctly, I don't even have to use extern and just define the variable as you would normally do. Then include the header and I can use the variable as I like (after giving it a value). And if I really need extern, don't forget to define it. `std::string var;` would be enough in the header. Include the header and then define it. `var = '....';` – nepp95 Mar 09 '18 at 21:02
  • @nepp95, nope, unless you use C++17 `inline` variables. If you defined the variable in the header, you are likely to have other problem - a double definition, if another file includes the same header. If `inline` variables are not for you, you will have to (one of the easiest solutions) create another `.cpp` file and define your variables there. – SergeyA Mar 09 '18 at 21:06
  • @nepp95 As a general rule, don't define variables or functions in headers (the big exception in C++ is templates). – melpomene Mar 09 '18 at 21:06
  • @melpomene, why? inline functions (and variables recently) are great way to provide header-only libraries, which is often of big benefit. – SergeyA Mar 09 '18 at 21:07
  • @SergeyA Yeah, true. I was assuming that newbies wouldn't reach for inline functions first. – melpomene Mar 09 '18 at 21:08
  • So then to conclude. The correct way to do this is leaving the extern declaration as it was. Then declaring the variable (preferably) in global scope. Then give it a value and use it as I wish? – nepp95 Mar 09 '18 at 21:10
1

The lines

Utility::BIN_PATH = path.substr(0, path.find_last_of('/'));
Utility::ROOT_PATH = Utility::BIN_PATH.substr(0, binPath.find_last_of('/'));

don't define the variables. They just assign values to them. To define them, use:

std::string Utility::BIN_PATH;
std::string Utility::ROOT_PATH;

in the global scope. Add similar line for EXE_PATH.

std::string Utility::EXE_PATH;

You can also define them using

namespace Utility
{
   std::string BIN_PATH;
   std::string ROOT_PATH;
   std::string EXE_PATH;
}

Make sure that those lines appear in a .cpp file, not in a .h file.

R Sahu
  • 204,454
  • 14
  • 159
  • 270