问题描述
如何在头文件中声明和定义对象并将其用于多个静态构建的库中?考虑下面的头文件,其中我实现了一个名为Logger
的类,打算在正在开发的几个库中像这样使用该类:
Utils::Logging::Logger.Display(true) << SOURCEINFO << "An exception occured: " << exp.what() << Utils::Logging::Save;
换句话说,我正在尝试实现和使用它,就像使用std :: cout一样。如您在下面看到的,我尝试了以下方法,但每次都失败:
- 尝试
static Log Logger;
会导致:
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
- 做
extern inline Log Logger;
的结果:
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
- 只需执行
extern Log Logger;
即可得出:
Severity Code Description Project File Line Suppression State
Error LNK2005 "class std::basic_ostream<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
在这一点上,我不确定是否正在尝试一些不可能的事情,或者只是做错了。
这是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
我在这里想念什么?
更新
按照@Peter的回答。我做了第一个选择,即在头文件中有Log Logger;
,然后在其中一个库中,例如FV.cpp
我做到了:
#include <Utility.h>
...
Utils::Logging::Log Logger;
此操作失败,并显示以下错误:
Severity Code Description Project File Line Suppression State
Error LNK2005 "class std::basic_ostream<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
更新2
这是一个最小的可还原示例:https://gofile.io/d/eJeADp
解决方法
记录器需要在.cpp文件中的某个地方实例化一次。
Log Logger;
仅将其声明为外部是不够的。
#include "logger.h"
Utils::Logging::Log Logger;
void Test() {
Logger << 42;
}
,
一个全局对象可以被声明多次,但是在整个程序中必须被定义一次。问题在于,头文件可以包含在多个编译单元(也称为源文件)中,这意味着头文件中的定义可以导致多个定义。
有一些选择。我给两个。
选项1-如果对象在文件范围内(在任何函数之外,而不是类成员),您都可以
extern Log Logger;
在头文件中,并将该头包含在多个编译单元中。 (如果标头包含定义(例如,类定义),则通常需要使用include保护措施来防止单个编译单元多次包含标头。)
然后,在整个项目的单个编译单元中,包含该标头以获取声明,然后对其进行定义
#include "header.h" // for the definition of Log and the declaration extern Log Logger
Log Logger; // definition of Logger
选项2-如果对象是类的静态成员,则在类定义中(在标头中)执行
static Log Logger;
然后在一个编译单元中执行
#include "header.h" // for the definition of the class or struct
Log WhateverClass::Logger;