【问题标题】:unable to split a class with static member functions in header and source file无法在头文件和源文件中拆分具有静态成员函数的类
【发布时间】:2023-03-11 13:59:01
【问题描述】:

我正在使用一个简单的日志类来调试我的代码。代码是从http://www.drdobbs.com/cpp/logging-in-c/201804215?pgno=1下载的,是一个完全在头文件中实现的日志类。

由于缺少目标文件导致的清晰度和构建困难,我想将实现与声明分开。那就是将代码实现移动到一个 cpp 文件中。

一旦我将实现细节移动到 .cpp 文件中,我最终会收到很多指向所有静态成员函数的“未定义引用”链接器错误。错误源于我的其余代码。我从任何地方调用日志记录功能。

问题是否与.cpp文件中的静态成员函数的内部链接有关?如果是这样,我必须通过哪些选项来实现文件拆分? 一种选择是删除类封装并将其重新实现为非 OO 代码。我也宁愿避免,还有别的吗?

ma​​in.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);
}

【问题讨论】:

  • 我没有看到包含记录器实现细节的 .cpp 文件?您可能应该发布需要帮助修复的不起作用的东西,而不是起作用的东西!
  • 您不能将模板实现放在源文件中 - 它们必须放在头文件中。
  • @immibis 真真假假。修复了
  • @Amit 我在试图弄清楚如何做到这一点时在某处读过。但这里另有说明:stackoverflow.com/questions/495021/…
  • 所有在线代码帮助的标准规则:只发布指示问题的最小程序,包括所有组件,以及您遇到的错误。

标签: c++ split singleton implementation static-members


【解决方案1】:

您需要显式实例化您正在使用的类型 - Log&lt;Output2FILE&gt;,所以输入以下行:

 template class Log<Output2FILE>;

在模板定义后的log.cpp 文件中的某个位置。您必须显式实例化您将与此模板一起使用的所有类型,或使模板代码可用于其他编译单元。

【讨论】:

  • 哥们,你对我来说根本不是 c++ 上帝!我已经迷失了一整天!您能否详细说明“或使模板代码可用于其他编译单元”的含义。我怎么做? (或提供教程链接。
  • @nass 使其可用意味着将其放入标题中。顺便说一句,您的模板上的大多数代码不依赖于 因此您可能希望制作基本的非模板类,将此类功能移到那里以 .cpp 常规方式实现。然后从该基础继承模板并仅实现真正使用T 的内容。在这种情况下,您可以将该模板化代码留在标头中,因为它会非常小。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-19
  • 2021-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多