Search code examples
c++variablesnamespacesdefinitionextern

C++ linker error for extern-declared global object


I have trouble using an extern-declared global object. As I understand it I should be able to declare an object as extern in a header file, define it in a source file, then use the object in any other source file where the header is included. However, with the following structure, I get a linker error saying that the logger symbol is undefined.

logger.hpp:

#ifndef MY_LOGGER_H
#define MY_LOGGER_H

namespace foo {
class Logger {
public:
    void log(int num);
};

extern Logger logger;
} // foo

#endif

logger.cpp:

#include "logger.hpp"
using namespace foo;

void Logger::log(int num) { /* Do stuff */ }

Logger logger;

main.cpp:

#include "logger.hpp"
using namespace foo;

int main() {
    logger.log(3); // arbitrary number
}

If I declare a Logger in main(), everything works fine, so the header and source files are being included and linked correctly. I get the same error using built-in types (e.g. int), so I don't think it's an issue with the Logger class itself either.

Appologies if this is a dumb question, I have a few years' experience in C++ but I've avoided global variables like the plague until now.


Solution

  • This declaration

    Logger logger;
    

    defines a variable in the global namespace.

    You need to write using qualified name

    #include "logger.hpp"
    
    using namespace foo;
    
    void Logger::log(int num) { /* Do stuff */ }
    
    Logger foo::logger;
    

    From the C++ 20 Standard (9.8.1.2 Namespace member definitions)

    2 Members of a named namespace can also be defined outside that namespace by explicit qualification (6.5.3.2) of the name being defined, provided that the entity being defined was already declared in the namespace and the definition appears after the point of declaration in a namespace that encloses the declaration’s namespace

    Here is a demonstrative program.

    #include <iostream>
    
    namespace foo {
        class Logger {
        public:
            void log(int num);
        };
    
        extern Logger logger;
    } // foo
    
    using namespace foo;
    
    void Logger::log(int num) { /* Do stuff */ }
    
    Logger foo::logger;
    
    int main() 
    {
        logger.log(3);
        
        return 0;
    }