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

时间:2019-04-18 18:25:55

标签: c++ templates circular-dependency template-classes

Error "Unterminated conditional directive" in cross-referencing headers

有关

我有一个Serializable模板类:

serializable.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

但是问题是Logger类使用LogMessage对象,该对象继承自Serializable(使用CRTP)。

Logger.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

LogMessage.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

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

我不知该如何进行。任何帮助表示赞赏!

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

2 个答案:

答案 0 :(得分:3)

有时,循环依赖关系需要对涉及的组件进行一些分析。找出为什么存在该圆,然后找出为什么不需要该圆。分析可以在多个级别进行。这是我要开始的两个级别。

(由于代码显然从实际代码中简化了,因此我将避免假定它显示了真正问题的程度。就此而言,感谢您不要为我们淹没过多的细节!代码足以大致说明问题。我的回答中的任何具体建议都旨在说明问题,不一定是最终解决方案。)


在一个级别上,您可以查看类的意图。忘记代码,专注于目标。类A在不知道类B是什么的情况下无法定义自己是否有意义?请记住,这比知道类B存在(这将等于前向定义,不需要标头)要强。如果不看代码就没有意义,那么也许您找到了可以解决的问题。诚然,模板的使用使事情变得复杂,因为整个实现都需要放在标头中。

例如,Serializable实际上应该能够定义自己,而无需知道序列化将要完成的工作(即Logger)。但是,它是一个模板,并且其实现需要能够记录错误。所以...很棘手。

还是,这是一个人们可以寻找解决方案的地方。一种可能是将错误记录分为仅处理字符串(已序列化的数据)的基本部分和可以将LogMessage转换为基本部分的字符串的转换层。反序列化过程中的错误强烈表明缺少要序列化的任何内容,因此日志记录可以直接转到基础代码。依赖圈会陷入困境:

Serializable -> LoggerBase
Logger -> LoggerBase
Logger -> LogMessage -> Serializable -> LoggerBase

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

例如,在示例代码中,Serializable需要LogMessage的原因是可以访问枚举LogMessage::ERROR。这不是需要整个LogMessage定义的强烈理由。也许像PostLogErrorSimple这样的包装器可以消除对严重性常数的了解?也许现实情况要比这复杂,但要点是,可以通过将依赖项推入源文件来避开某些依赖项。有时源文件是用于其他类的。

另一个示例来自Logger类。此类需要LogMessage才能访问LogMessage::Severity枚举(即以ERROR作为其值之一的枚举)。这也不是需要整个类定义的强烈理由。也许应该在其他地方定义枚举?作为Logger的一部分?或者也许根本不在类定义中?如果这种方法行得通,则依赖项圈子会分成多个链:

Serializable -> Severity
Serializable -> Logger -> Severity // To get the PostLogMessageSimple function
Logger -> Severity

理想情况下,一旦处理了枚举,Logger头就可以通过仅LogMessage的前向声明就可以通过,而不必包括完整的头。 (前向声明足以通过引用接收对象。并且可能是完整的Logger定义中有些函数带有LogMessage参数。)

答案 1 :(得分:0)

如果您仅在类中有声明并且不按顺序定义方法,则应该可以使它起作用:

#pragma once
#ifndef SERIALIZABLE_H
#define SERIALIZABLE_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);
private:
    static boost::property_tree::ptree GetPropertyTreeFromJsonString(const std::string & jsonStr);
}

#include "Logger.h"

template < typename T >
inline bool Serializable<T>::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;
        }
    }

template < typename T >
inline boost::property_tree::ptree Serializable<T>::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

这还有使您的类界面更清晰的额外好处。

相关问题