无法在头文件和源文件中使用静态成员函数拆分 class
unable to split a class with static member functions in header and source file
我正在使用一个简单的日志 class 来调试我的代码。代码已从 http://www.drdobbs.com/cpp/logging-in-c/201804215?pgno=1 下载,是完全在头文件中实现的日志 class。
为了清楚起见和由于缺少目标文件而导致的构建困难,我想将实现与声明分开。那就是将代码实现移动到一个cpp文件中。
将实现细节移至 .cpp 文件后,我最终会收到所有静态成员函数的大量 "undefined reference" 链接器错误。错误源于我的其余代码。我从任何地方调用日志记录功能。
问题是否与.cpp 文件中静态成员函数的内部链接有关?如果是这样,我有什么选择来实现拆分文件?
一种选择是删除 class 封装并将其重新实现为非 OO 代码。我也宁愿避免那个,还有别的吗?
main.cpp
int main(int argc, char* argv[])
{
FILELog::ReportingLevel() = FILELog::FromString(argv[1] ? argv[1] : "DEBUG1");
const int count = 3;
FILE_LOG(logDEBUG) << "A loop with " << count << " iterations";
for (int i = 0; i != count; ++i)
{
FILE_LOG(logDEBUG1) << "the counter i = " << i;
}
return 0;
}
log.hpp
#ifndef __LOG_H__
#define __LOG_H__
#include <sstream>
#include <string>
#include <stdio.h>
#ifndef FILELOG_MAX_LEVEL
#define FILELOG_MAX_LEVEL logDEBUG4
#endif
#define FILE_LOG(level) \
if (level > FILELOG_MAX_LEVEL) ;\
else if (level > FILELog::ReportingLevel() || !Output2FILE::Stream()) ; \
else FILELog().Get(level)
enum TLogLevel
{
logERROR,
logWARNING,
logINFO,
logDEBUG,
logDEBUG1,
logDEBUG2,
logDEBUG3,
logDEBUG4
};
class Output2FILE
{
public:
static FILE*& Stream();
static void Output(const std::string& msg);
};
template <typename T>
class Log
{
public:
Log();
virtual ~Log();
std::ostringstream& Get(TLogLevel level = logINFO);
public:
static TLogLevel& ReportingLevel();
static std::string ToString(TLogLevel level);
static TLogLevel FromString(const std::string& level);
protected:
std::ostringstream os;
private:
Log(const Log&);
Log& operator =(const Log&);
};
//class FILELog : public Log<Output2FILE> {};
typedef Log<Output2FILE> FILELog;
#endif //__LOG_H__
log.cpp
#include <sys/time.h>
#include "log.hpp"
inline std::string NowTime()
{
char buffer[11];
time_t t;
time(&t);
//tm r = {0};
tm r = {0,0,0,0,0,0,0,0,0,0,0};
strftime(buffer, sizeof(buffer), "%X", localtime_r(&t, &r));
struct timeval tv;
gettimeofday(&tv, 0);
char result[100] = {0};
sprintf(result, "%s.%03ld", buffer, (long)tv.tv_usec / 1000);
return result;
}
//----------------------------------------------------------------------
template <typename T>
Log<T>::Log()
{
}
template <typename T>
std::ostringstream& Log<T>::Get(TLogLevel level)
{
os << "- " << NowTime();
os << " " << ToString(level) << ": ";
os << std::string(level > logDEBUG ? level - logDEBUG : 0, '\t');
return os;
}
template <typename T>
Log<T>::~Log()
{
os << std::endl;
T::Output(os.str());
}
template <typename T>
TLogLevel& Log<T>::ReportingLevel()
{
static TLogLevel reportingLevel = logDEBUG4;
return reportingLevel;
}
template <typename T>
std::string Log<T>::ToString(TLogLevel level)
{
static const char* const buffer[] = {"ERROR", "WARNING", "INFO", "DEBUG", "DEBUG1", "DEBUG2", "DEBUG3", "DEBUG4"};
return buffer[level];
}
template <typename T>
TLogLevel Log<T>::FromString(const std::string& level)
{
if (level == "DEBUG4")
return logDEBUG4;
if (level == "DEBUG3")
return logDEBUG3;
if (level == "DEBUG2")
return logDEBUG2;
if (level == "DEBUG1")
return logDEBUG1;
if (level == "DEBUG")
return logDEBUG;
if (level == "INFO")
return logINFO;
if (level == "WARNING")
return logWARNING;
if (level == "ERROR")
return logERROR;
Log<T>().Get(logWARNING) << "Unknown logging level '" << level << "'. Using INFO level as default.";
return logINFO;
}
//----------------------------------------------------------------------
inline FILE*& Output2FILE::Stream()
{
static FILE* pStream = stderr;
return pStream;
}
inline void Output2FILE::Output(const std::string& msg)
{
FILE* pStream = Stream();
if (!pStream)
return;
fprintf(pStream, "%s", msg.c_str());
fflush(pStream);
}
您需要显式实例化您正在使用的类型 - Log<Output2FILE>
,因此输入以下行:
template class Log<Output2FILE>;
定义模板后 log.cpp
文件中的某处。您将必须显式实例化将用于此模板的所有类型,或使模板代码可用于其他编译单元。
我正在使用一个简单的日志 class 来调试我的代码。代码已从 http://www.drdobbs.com/cpp/logging-in-c/201804215?pgno=1 下载,是完全在头文件中实现的日志 class。
为了清楚起见和由于缺少目标文件而导致的构建困难,我想将实现与声明分开。那就是将代码实现移动到一个cpp文件中。
将实现细节移至 .cpp 文件后,我最终会收到所有静态成员函数的大量 "undefined reference" 链接器错误。错误源于我的其余代码。我从任何地方调用日志记录功能。
问题是否与.cpp 文件中静态成员函数的内部链接有关?如果是这样,我有什么选择来实现拆分文件? 一种选择是删除 class 封装并将其重新实现为非 OO 代码。我也宁愿避免那个,还有别的吗?
main.cpp
int main(int argc, char* argv[])
{
FILELog::ReportingLevel() = FILELog::FromString(argv[1] ? argv[1] : "DEBUG1");
const int count = 3;
FILE_LOG(logDEBUG) << "A loop with " << count << " iterations";
for (int i = 0; i != count; ++i)
{
FILE_LOG(logDEBUG1) << "the counter i = " << i;
}
return 0;
}
log.hpp
#ifndef __LOG_H__
#define __LOG_H__
#include <sstream>
#include <string>
#include <stdio.h>
#ifndef FILELOG_MAX_LEVEL
#define FILELOG_MAX_LEVEL logDEBUG4
#endif
#define FILE_LOG(level) \
if (level > FILELOG_MAX_LEVEL) ;\
else if (level > FILELog::ReportingLevel() || !Output2FILE::Stream()) ; \
else FILELog().Get(level)
enum TLogLevel
{
logERROR,
logWARNING,
logINFO,
logDEBUG,
logDEBUG1,
logDEBUG2,
logDEBUG3,
logDEBUG4
};
class Output2FILE
{
public:
static FILE*& Stream();
static void Output(const std::string& msg);
};
template <typename T>
class Log
{
public:
Log();
virtual ~Log();
std::ostringstream& Get(TLogLevel level = logINFO);
public:
static TLogLevel& ReportingLevel();
static std::string ToString(TLogLevel level);
static TLogLevel FromString(const std::string& level);
protected:
std::ostringstream os;
private:
Log(const Log&);
Log& operator =(const Log&);
};
//class FILELog : public Log<Output2FILE> {};
typedef Log<Output2FILE> FILELog;
#endif //__LOG_H__
log.cpp
#include <sys/time.h>
#include "log.hpp"
inline std::string NowTime()
{
char buffer[11];
time_t t;
time(&t);
//tm r = {0};
tm r = {0,0,0,0,0,0,0,0,0,0,0};
strftime(buffer, sizeof(buffer), "%X", localtime_r(&t, &r));
struct timeval tv;
gettimeofday(&tv, 0);
char result[100] = {0};
sprintf(result, "%s.%03ld", buffer, (long)tv.tv_usec / 1000);
return result;
}
//----------------------------------------------------------------------
template <typename T>
Log<T>::Log()
{
}
template <typename T>
std::ostringstream& Log<T>::Get(TLogLevel level)
{
os << "- " << NowTime();
os << " " << ToString(level) << ": ";
os << std::string(level > logDEBUG ? level - logDEBUG : 0, '\t');
return os;
}
template <typename T>
Log<T>::~Log()
{
os << std::endl;
T::Output(os.str());
}
template <typename T>
TLogLevel& Log<T>::ReportingLevel()
{
static TLogLevel reportingLevel = logDEBUG4;
return reportingLevel;
}
template <typename T>
std::string Log<T>::ToString(TLogLevel level)
{
static const char* const buffer[] = {"ERROR", "WARNING", "INFO", "DEBUG", "DEBUG1", "DEBUG2", "DEBUG3", "DEBUG4"};
return buffer[level];
}
template <typename T>
TLogLevel Log<T>::FromString(const std::string& level)
{
if (level == "DEBUG4")
return logDEBUG4;
if (level == "DEBUG3")
return logDEBUG3;
if (level == "DEBUG2")
return logDEBUG2;
if (level == "DEBUG1")
return logDEBUG1;
if (level == "DEBUG")
return logDEBUG;
if (level == "INFO")
return logINFO;
if (level == "WARNING")
return logWARNING;
if (level == "ERROR")
return logERROR;
Log<T>().Get(logWARNING) << "Unknown logging level '" << level << "'. Using INFO level as default.";
return logINFO;
}
//----------------------------------------------------------------------
inline FILE*& Output2FILE::Stream()
{
static FILE* pStream = stderr;
return pStream;
}
inline void Output2FILE::Output(const std::string& msg)
{
FILE* pStream = Stream();
if (!pStream)
return;
fprintf(pStream, "%s", msg.c_str());
fflush(pStream);
}
您需要显式实例化您正在使用的类型 - Log<Output2FILE>
,因此输入以下行:
template class Log<Output2FILE>;
定义模板后 log.cpp
文件中的某处。您将必须显式实例化将用于此模板的所有类型,或使模板代码可用于其他编译单元。