如何处理模板类标头中的循环 #include 调用
How can I handle circular #include calls in a template class header?
与交叉引用标头中的错误"未终止的条件指令"相关
我有一个可序列化的模板类:
可序列化.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 类使用从 Serializable 继承的 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
日志消息.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"移动到可序列化.cpp),因为可序列化是一个模板类,因此需要在头文件中定义。
我不知道该如何进行。任何帮助不胜感激!
编辑: 我还考虑在 serializable.h 中使用 Logger 和 LogMessage 的前向声明,但由于我在 Logger 中调用静态方法并使用 LogMessage::Searnity,这不起作用。
有时,循环依赖关系需要对所涉及的组件进行一些分析。弄清楚为什么这个圆存在,然后弄清楚为什么它不需要存在。分析可以在多个级别进行。以下是我要开始的两个级别。
(由于代码显然是从真实代码简化而来的,因此我将避免假设它显示了真正问题的严重程度。说到这里,感谢您没有用压倒性的细节淹没我们!该代码足以说明一般问题。我的回答中的任何具体建议同样是为了说明要点,不一定是最终的解决方案。
在一个层面上,您可以查看类的意图。忘记代码,专注于目的。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
参数。
如果你的类中只有声明并定义出线的方法,你应该能够让它工作:
#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
这还有一个额外的好处,那就是让你的类界面更清晰。
考虑 N 个标头,每个标头定义一个类。 让 N 个标头中的每一个都包含一个模板或内联函数,因此需要在标头中定义,该函数使用所有其他 N-1 类的声明。
在下面的示例中,我使用 N=4、结构而不是类和内联成员函数而不是模板来实际使用其他结构。当然,每个结构定义也使用(需要)前向声明,但我省略了它,因为它与模式无关。
啊:
#ifndef A_H
#define A_H
// Forward declare everything.
struct A;
struct B;
struct C;
struct D;
struct A {
void use();
};
#endif // A_H
#ifndef B_H
#include "B.h"
#endif
#ifndef C_H
#include "C.h"
#endif
#ifndef D_H
#include "D.h"
#endif
#ifndef A_defs_H
#define A_defs_H
inline void A::use()
{
// Use everything.
B x; C y; D z;
}
#endif // A_defs_H
B.h:
#ifndef B_H
#define B_H
// Forward declare everything.
struct A;
struct B;
struct C;
struct D;
struct B {
void use();
};
#endif // B_H
#ifndef A_H
#include "A.h"
#endif
#ifndef C_H
#include "C.h"
#endif
#ifndef D_H
#include "D.h"
#endif
#ifndef B_defs_H
#define B_defs_H
inline void B::use()
{
// Use everything.
A x; C y; D z;
}
#endif // B_defs_H
C.h:
#ifndef C_H
#define C_H
// Forward declare everything.
struct A;
struct B;
struct C;
struct D;
struct C {
void use();
};
#endif // C_H
#ifndef A_H
#include "A.h"
#endif
#ifndef B_H
#include "B.h"
#endif
#ifndef D_H
#include "D.h"
#endif
#ifndef C_defs_H
#define C_defs_H
inline void C::use()
{
// Use everything.
A x; B y; D z;
}
#endif // C_defs_H
D.h:
#ifndef D_H
#define D_H
// Forward declare everything.
struct A;
struct B;
struct C;
struct D;
struct D {
void use();
};
#endif // D_H
#ifndef A_H
#include "A.h"
#endif
#ifndef B_H
#include "B.h"
#endif
#ifndef C_H
#include "C.h"
#endif
#ifndef D_defs_H
#define D_defs_H
inline void D::use()
{
// Use everything.
A x; B y; C z;
}
#endif // D_defs_H
每个#include
周围的防护装置对于避免无限的包含深度是必要的。请注意,只需两个标题 (N = 2),您也可以将包含放在前一个块中。例如:
啊:
#ifndef A_H
#define A_H
// Forward declare everything.
struct A;
struct B;
struct A {
void use();
};
#include "B.h"
#endif // A_H
#ifndef A_defs_H
#define A_defs_H
inline void A::use()
{
// Use everything.
B x;
}
#endif // A_defs_H
同样,对于B.h
也可以与N = 2
一起使用.
- 当调用switch语句中的函数时(即使函数不包含循环),似乎是永不结束的循环的问题
- 循环中的条件:为什么每次都调用strlen(),而vector.size()只调用一次
- 当 A 在 for 循环中调用函数 B 时,如何计算函数 A 的空间复杂度?
- 如何循环调用模板函数?
- 将手写循环转换为标准库调用
- OpenMP 内部的函数调用用于循环
- 在循环中调用同一虚函数的开销
- 当数组位于两个循环之间时,您可以调用数组的 void 函数吗?
- 循环中本地对象的析构函数是否保证在下一次迭代之前被调用?
- 从基于迭代器的for循环转换后,如何在map::find()中调用方法
- 实现递归函数,避免由 C++ 中 include 的循环调用(没有 #pragma 一次)引起的无限循环输入
- 通过for循环调用函数
- 嵌套的循环调用类成员函数
- 如何在异构类的实例上循环调用具有相同名称和参数的方法
- 如何将数字"Bind"到一串单词/短语中,以便我可以循环调用它?
- 将迭代器循环调用end()多次
- c++使用for循环调用不同的类方法
- 递归调用,而不是多个for循环调用
- 我需要以一种可以从循环调用构造函数的方式组织我的类
- OpenMP从并行for循环调用并行化函数