如何处理模板类标头中的循环 #include 调用?

Kev*_*Rak 2 c++ templates circular-dependency template-classes

与交叉引用标头中的错误“未终止的条件指令”相关

我有一个可序列化的模板类:

可序列化.h

#pragma once
#ifndef SERIALIZABLE_H
#define SERIALIZABLE_H
#include "Logger.h"
#include <string>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>

template<class T>
class Serializable {
public:
    static bool Deserialize(Serializable<T>* object, std::string serializedObject) {
        try {
            return object->SetValuesFromPropertyTree(GetPropertyTreeFromJsonString(serialized));
        } catch (...) {
            std::string message = boost::current_exception_diagnostic_information();
            Logger::PostLogMessageSimple(LogMessage::ERROR, message);
            std::cerr << message << std::endl;
        }
    }
private:
    static boost::property_tree::ptree GetPropertyTreeFromJsonString(const std::string & jsonStr) {
        std::istringstream iss(jsonStr);
        boost::property_tree::ptree pt;
        boost::property_tree::read_json(iss, pt);
        return pt;
    }
}
#endif // SERIALIZABLE_H
Run Code Online (Sandbox Code Playgroud)

但问题是 Logger 类使用了一个继承自 Serialized 的 LogMessage 对象(使用 CRTP)。

记录器.h

#pragma once
#ifndef LOGGER_H
#define LOGGER_H
#include "LogMessage.h"

class Logger {
public:
    static void PostLogMessageSimple(LogMessage::Severity severity, const std::string & message);
}
#endif // LOGGER_H
Run Code Online (Sandbox Code Playgroud)

日志消息.h

#pragma once
#ifndef LOGMESSAGE_H
#define LOGMESSAGE_H
#include "serializable.h"

class LogMessage : public Serializable<LogMessage> {
public:
    enum Severity {
        DEBUG,
        INFO,
        WARN,
        ERROR
    };
private:
    std::string m_timestamp;
    std::string m_message;

    friend class Serializable<LogMessage>;
    virtual boost::property_tree::ptree GetNewPropertyTree() const;
    virtual bool SetValuesFromPropertyTree(const boost::property_tree::ptree & pt);
}

#endif // LOGMESSAGE_H
Run Code Online (Sandbox Code Playgroud)

这里的问题是每个文件都包含另一个文件,这会导致构建错误。不幸的是,我无法使用上述链接问题的解决方案(将#include“Logger.h”移动到Serialized.cpp),因为Serialized是一个模板类,因此需要在头文件中定义。

我不知道如何继续。任何帮助表示赞赏!

编辑:我还考虑过在serialized.h中使用Logger和LogMessage的前向声明,但由于我在Logger中调用静态方法并使用LogMessage::Severity,所以这是行不通的。

JaM*_*MiT 5

有时,循环依赖需要对所涉及的组件进行一些分析。弄清楚圆为什么存在,然后再弄清楚为什么它不需要存在。分析可以在多个层面上进行。这是我要开始的两个级别。

(由于代码显然是从真实代码简化而来,因此我将避免假设它显示了真正问题的程度。说到这里,感谢您没有用压倒性的细节淹没我们!代码足以说明一般问题。我的答案中的任何具体建议同样旨在说明要点,不一定是最终解决方案。)


一方面,您可以查看课程的意图。忘记代码并专注于目的。如果 A 类在不知道 B 类是什么的情况下无法定义自己,这是否有意义?请记住,这比知道 B 类存在更强大(这相当于前向定义,不需要标头)。如果不看代码就没有意义,那么也许您找到了可以做的事情。不可否认,模板的使用使事情变得复杂,因为整个实现都需要位于标头中。

例如, aSerializable确实应该能够在不知道序列化将做什么的情况下定义自己(即Logger)。然而,它是一个模板,它的实现需要能够记录错误。所以...很棘手。

尽管如此,这是一个人们可以寻找解决方案的地方。一种可能性可能是将错误日志记录到仅处理字符串(已序列化的数据)的基础部分和可以将 a 转换LogMessage为基础部分的字符串的转换层中。反序列化期间的错误强烈表明缺少任何可序列化的内容,因此日志记录可以直接进入基础部分。依赖环会分解成链条:

Serializable -> LoggerBase
Logger -> LoggerBase
Logger -> LogMessage -> Serializable -> LoggerBase
Run Code Online (Sandbox Code Playgroud)

在另一个层面上,您可以详细查看代码,而不必过多担心目的。您的标头 A 包括标头 B – 为什么?A 的哪些部分实际上使用了 B 的某些东西?实际使用了 B 的哪些部分?如果您需要更好地直观地了解这种依赖关系所在,请绘制一个图表。然后考虑一些目的。这些部分是否定义在适当的位置?还有其他的可能性吗?

例如,在示例代码中,Serializable需要的原因LogMessage是访问 enumerate LogMessage::ERROR。这并不是需要整个LogMessage定义的有力理由。也许像这样的包装器PostLogErrorSimple可以消除了解严重性常量的需要?也许现实比这更复杂,但关键是可以通过将依赖项推入源文件来回避某些依赖项。有时源文件适用于不同的类。

另一个例子来自Logger课堂。该类需要LogMessage访问枚举(即具有其值之一的LogMessage::Severity枚举)。ERROR这也不是需要整个类定义的充分理由。也许枚举应该在其他地方定义?也许作为一部分Logger?或者可能根本不在类定义中?如果这种方法有效,依赖环就会分解成链条:

Serializable -> Severity
Serializable -> Logger -> Severity // To get the PostLogMessageSimple function
Logger -> Severity
Run Code Online (Sandbox Code Playgroud)

理想情况下,一旦处理好枚举,Logger标头就可以通过前向声明来完成,LogMessage而不是包含完整的标头。(前向声明足以通过引用接收对象。并且大概完整的Logger定义将包含一些带有LogMessage参数的函数。)