0

How is it possible to declare and define object in a header file and use that in several statically built libs? Consider the following Headerfile in which I have implemented a class called Logger which I intend on using like this in several libraries that I'm developing :

Utils::Logging::Logger.Display(true) << SOURCEINFO << "An exception occured: " << exp.what() << Utils::Logging::Save;

In other words, I'm trying to implement and use it kind of like how std::cout is used. As you can see below, I tried the following approaches and failed each time :

  1. Trying static Log Logger; results in :
Severity    Code    Description Project File    Line    Suppression State
Error   LNK2005 "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl Utils::Logging::operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Utils::Logging::Log const &)" (??6Logging@Utils@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AEAV23@AEBVLog@01@@Z) already defined in FV.lib(FV.obj)   FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\FV_Test_Lib\AntiSpoofer.lib(AntiSpoofer.obj)    1   
Error   LNK2001 unresolved external symbol "class Utils::Logging::Log Utils::Logging::Logger" (?Logger@Logging@Utils@@3VLog@12@A)   FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\FV_Test_Lib\FV.lib(FV.obj)  1   
Error   LNK1120 1 unresolved externals  FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\x64\Release\FV_Test_Lib.exe 1   
  1. Doing extern inline Log Logger; results :
Severity    Code    Description Project File    Line    Suppression State
Error   C7526   'Logger': inline variable is undefined  FV  D:\Codes\fac_ver\cpp\port\LibtorchPort\Dependencies\include\Utility.h   513 
  1. And simply doing extern Log Logger; results in :
Severity    Code    Description Project File    Line    Suppression State
Error   LNK2005 "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl Utils::Logging::operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Utils::Logging::Log const &)" (??6Logging@Utils@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AEAV23@AEBVLog@01@@Z) already defined in FV.lib(FV.obj)   FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\FV_Test_Lib\AntiSpoofer.lib(AntiSpoofer.obj)    1   
Error   LNK2001 unresolved external symbol "class Utils::Logging::Log Utils::Logging::Logger" (?Logger@Logging@Utils@@3VLog@12@A)   FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\FV_Test_Lib\FV.lib(FV.obj)  1   
Error   LNK1120 1 unresolved externals  FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\x64\Release\FV_Test_Lib.exe 1   

At this point I'm not sure if I'm trying something impossible or I simply doing it wrong.
Here is the Utility.h:

#ifndef UTILITY_H
#define UTILITY_H

/* If we are we on Windows, we want a single define for it.*/
#if !defined(_WIN32) && (defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__))
#define _WIN32 //for both 32 and 64 bit. use _WIN64 for 64 bit only
#endif // _WIN32 

#if defined(__GNUC__) || defined(unix) || defined(__unix__) || defined(__unix)
# define _UNIX 
#endif // _UNIX

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <chrono>
#include <iomanip>
#include <type_traits>
#include <typeinfo>

namespace Utils
{
    namespace Logging
    {

#ifdef _WIN32
    #define FUNC_SIG __FUNCSIG__
#endif

#ifdef _UNIX
    #define FUNC_SIG __PRETTY_FUNCTION__
#endif

#define SOURCEINFO __FILE__<<":"<<__LINE__<<":"<<FUNC_SIG<<": "

        template<typename... Args>
        void LogMsg(std::string logFilename = "FVLog.txt", bool display = true, Args... args)
        {
            std::ofstream logFile;
            logFilename = (logFilename == "") ? "FVLog.txt" : logFilename;
            logFile.open(logFilename, std::ios::out | std::ios::app);
            
            logFile << DateTime::Now(false);

            if (display)
                ((std::cout << args), ...);
            //cpp17 fold-expression:, recursively send each value to be saved in our stream
            ((logFile << args), ...);
        }

        enum class Mode
        {
            Save = 0,
            Load = 1
        };
        static constexpr Mode Save = Mode::Save;

        class Log
        {
        private:
            std::stringstream stream;
            bool isNew = true;
            bool displayResults=false;
            bool benchMode = false;

        public:

            template <typename T>
            Log& operator<<(const T& value)
            {
                if (isNew)
                {
                    stream << DateTime::Now(false) << " ";
                    isNew = false;
                }

                if constexpr (std::is_same<T, Logging::Mode>::value) 
                {
                    if (value == Mode::Save)
                        Save();

                    if (displayResults)
                        std::cout << std::endl;;
                }
                else
                {
                    stream << value;
                }
                
                if (displayResults)
                    std::cout << stream.str();
                
                return *this;
            }
            
            void Save(std::string logFilename= "FVLog.txt")
            {
                isNew = true;

                if (benchMode)
                    return;

                std::ofstream logFile;
                logFilename = (logFilename == "") ? "FVLog.txt" : logFilename;
                logFile.open(logFilename, std::ios::out | std::ios::app);

                logFile << this->Get() << std::endl;
                
                this->stream.clear();
                this->stream.str("");
 
                logFile.close();
            }
            
            Log& Display(bool showOutput, bool benchMode=false)
            {
                displayResults = showOutput;
                this->benchMode = benchMode;

                return *this;
            }
            std::string Get() const
            {
                return this->stream.str();
            }

            friend std::ostream& operator<<(std::ostream& os, const Log& log);
       };

       std::ostream& operator<<(std::ostream& os, const Log& log)
       {
           os << log.Get();
           return os;
       }
       
       extern Log Logger;
    }
}
#endif // !UTILITY_H

What am I missing here?

Update

Following @Peter's answer. I did the first option, that is in the header file we have Log Logger; and then in one of the libs e.g. FV.cpp I did :

#include <Utility.h>
...
Utils::Logging::Log Logger;

this fails with the following error :

Severity    Code    Description Project File    Line    Suppression State
Error   LNK2005 "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl Utils::Logging::operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Utils::Logging::Log const &)" (??6Logging@Utils@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AEAV23@AEBVLog@01@@Z) already defined in FV.lib(FV.obj)   FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\FV_Test_Lib\AntiSpoofer.lib(AntiSpoofer.obj)    1   
Error   LNK2001 unresolved external symbol "class Utils::Logging::Log Utils::Logging::Logger" (?Logger@Logging@Utils@@3VLog@12@A)   FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\FV_Test_Lib\FV.lib(FV.obj)  1   
Error   LNK1120 1 unresolved externals  FV_Test_Lib D:\Codes\rika\cpp\port\LibtorchPort\x64\Release\FV_Test_Lib.exe 1   

enter image description here

Update 2

Here is a Minimal Repreducible Example : https://gofile.io/d/eJeADp

Hossein
  • 24,202
  • 35
  • 119
  • 224
  • 2
    You are doing it wrong. You should create a singleton providing global access to Logger instead of global variable. – user7860670 Sep 30 '20 at 09:20
  • 4
    According to [cppreference - inline specifier](https://en.cppreference.com/w/cpp/language/inline), just `inline Log Logger;` should be sufficient. (Take a look at the `inline std::atomic counter(0);` in the example which seems to provide just the same.) This is for C++17 or higher, of course, but I believe this would be sufficient to you (if I remember right). – Scheff's Cat Sep 30 '20 at 09:20
  • Can you not provide a [mcve]? Saving/loading is irrelevant to your question. This should be easy to reproduce with a one-line header and a few lines in a source file? – Asteroids With Wings Sep 30 '20 at 09:42
  • @user7860670 This is fine. Imagine recommending a singleton then saying "you are doing it wrong" :P – Asteroids With Wings Sep 30 '20 at 09:44
  • @AsteroidsWithWings Could you please be a bit more elaborate what should I be implementing exactly? I provided the header file so its clear. do you need a vs solution? – Hossein Sep 30 '20 at 09:55
  • @AsteroidsWithWings There is nothing wrong with recommending a singleton. – user7860670 Sep 30 '20 at 10:24
  • @user7860670 They're called "simpletons" for a reason ;) – Asteroids With Wings Sep 30 '20 at 10:30

2 Answers2

2

Logger needs to be instantiated somewhere once in a .cpp file.

Log Logger;

it is not enough just to declare it extern.

#include "logger.h"

Utils::Logging::Log Logger;

void Test() {
  Logger << 42;
}
Surt
  • 15,501
  • 3
  • 23
  • 39
  • I tried `Log Logger; extern Log Logger;` but that doesnt help either. – Hossein Sep 30 '20 at 09:18
  • Or any other .cpp file that is linked. – Surt Sep 30 '20 at 09:20
  • I did this as well, still its not working. I uploaded a gif to the question. did you mean sth like tha? – Hossein Sep 30 '20 at 09:58
  • There is missing a declaration of DateTime when I try to compile it and if I comment out the two lines with DateTime it compiles. – Surt Sep 30 '20 at 10:17
  • @Surf you can get the full implementation here : https://stackoverflow.com/questions/64131310 – Hossein Sep 30 '20 at 10:19
  • Please note that, in my scenario, there are 2 Libs that are using this. AntiSpoofer uses Utility.h and FV which also uses Antispoofer, also uses Utility. and both of them are statically built. does such a scenario still build for you? – Hossein Sep 30 '20 at 10:20
  • You are missing an #include And with my setup both _WIN32 and _UNIX are defined. – Surt Sep 30 '20 at 10:23
  • Place the #include "Utilities" at the top of your FV file so you can see which files are missing. – Surt Sep 30 '20 at 10:24
  • With statically build do you mean as .dll/.so or as .obj/.a? – Surt Sep 30 '20 at 10:38
  • as `.obj.a` in linux term and .lib in windows term. . that didnt help either by the way! – Hossein Sep 30 '20 at 10:40
  • I created an MRE you can download it from here if you like : file.io/t5feWSUeuNIw – Hossein Sep 30 '20 at 12:14
2

An global object may be declared many times, but must be defined exactly once in the whole program. The problem is that headers can be included by multiple compilation units (aka source files), which means that definitions in a header can result in multiple definitions.

There are a few options. I'll give two.

Option 1 - If the object is at file scope (outside any function, not a class member) you can do

 extern Log Logger;

in a header file, and include that header in multiple compilation units. (include guards are often needed to prevent multiple inclusion of headers by a single compilation unit, if that header contains definitions (e.g. a class definition)).

Then, in a single compilation unit in your whole project, include that header to get the declaration, and then define it

 #include "header.h"     // for the definition of Log and the declaration extern Log Logger
 
 Log Logger;               // definition of Logger

Option 2 - If the object is a static member of a class, then in the class definition (within a header) do

 static Log Logger;

and then in a single compilation unit do

 #include "header.h"     // for the definition of the class or struct

 Log WhateverClass::Logger;
Peter
  • 35,646
  • 4
  • 32
  • 74
  • Thanks, I did the option 1. already as you can see in the header file I wrote `extern Log Logger;` and then for example went to `FV.cpp` included and wrote `Utils::Logging::Log logger;` but then again I get `Already defined in FV.obj` error I updated the qustion with the full error message. – Hossein Sep 30 '20 at 09:43
  • its not declared inside of a class, its declared outside of it! its in the second namespace `Logging` – Hossein Sep 30 '20 at 10:44
  • Unfortunately, I can't reproduce what you describe now. Looks like it's time for you to start simplifying the code, to produce a [mcve]. There's a fair amount of extraneous code there, and some of your macro definitions don't work with my setup. Eliminate all code that isn't directly relevant to the specific problem, to produce the smallest possible sample of code that exhibits the symptom. In doing that, you might have an "Aha!" moment, and be able to solve the problem yourself. If not, you'll have a workable sample of code that others can use to recreate the problem and help you. – Peter Sep 30 '20 at 11:01
  • Thanks peter, I really apprecaite your time and kind help. I'll make a MRE then and reply when its done. – Hossein Sep 30 '20 at 11:02
  • I created a MRE you can download it from here if you like : https://file.io/t5feWSUeuNIw – Hossein Sep 30 '20 at 12:13