我正在处理C++中的一个循环依赖问题。
情况如下:
libA.so:
- Body.cpp
- Header.cpp
- DataObject.cpp
- DataObject::read(boost::asio::streambuf* data)
{
boost::asio::streambuf data;
....
body = (new DataConverter<Body>)->convert(&data);
header = (new DataConverter<Header>)->convert(&data);
}
libB.so:
- DataConverter.cpp
-> DataConverter<T>
-> T* DataConverter<T>::convert(boost::asio::streambuf* data)
libA.so <-> libB.so
存在循环依赖关系,因为libA使用libB中的Converter类,而libB现在需要了解需要转换的libA的对象类型,因为DataConverter::convert返回Body或Header对象。
我曾想过用正向声明来解决这个问题,但这对我来说似乎不是最干净的解决方案。总之,我的计划是提供一个可扩展的DataConverter解决方案。
你们会建议什么作为最佳实践?完全不同的设计也很受欢迎:)
最佳,Sebastian
如果您需要一个类模板DataConverter
,那么它不能是任何编译库的一部分。它必须通过包含文件提供。一旦将DataConverter
代码放入libA
和libB
都使用的标头中,循环依赖关系就会消失。
您的设计似乎有缺陷。如果名为A和B的两个库相互依赖,则意味着它们必须始终一起发货。如果它们必须始终一起发货,这意味着它们在逻辑上是同一接口的一部分。这意味着事实上,你只有一个图书馆。
没有足够的信息来说明什么是最好的解决方案,但这里有一些提示:
- 合并这些库
- 使一个库依赖于另一个库,例如,通过将DataConverter移动到libA
- 创建一个实用程序库,依赖于这两个
- 使用模板或虚拟类在libB中创建一个适当的接口,并使libA依赖于libB。后者(虚拟类)很可能是动态链接库中更好的选择
您可能想要创建定义接口的抽象基类,并相互隐藏实现(派生类)。
一些替代方案:
-
DataConverter
是一个完全通用的实现,它将在编译时用适当的类型在libA.so中实例化。这是一个典型的c++式解决方案。来自libA(或其他)的"可转换"类型必须满足某些Convertable
概念,而DataConverter
的完全模板化实现将使用 -
依赖反转,如JohnB所提出的。您基本上可以实现相同的目标,但需要在运行时使用接口、实现和注册/解析。还有很多工作要做,但可扩展、ABI可实现、可部署为库等
-
两者的某种巧妙结合,类似于
Boost.Serialization
。然而,这很难实现,也很容易打破。。。