模块化C++设计
Modular C++ Design
我正在设计一个包含多个模块的工具包。我正在努力使模块尽可能独立,这样它们甚至可以独立编译(例如作为库)。
其中一个模块是logging
,另一个模块则是geometry
。现在,geometry
中的一个基类接收到一个指向logging
对象的指针,然后使用它来记录数据:
#include "../logging/logger.h"
class GeometryBase {
public:
//...
void do_something() { if (logger) logger->debug("doing something"); }
void setLogger(Logger* logger) {//...};
private:
Logger* logger = nullptr;
};
因此,我需要包含../logging/logger.h
,这意味着编译这个模块需要logging
头。有没有办法绕过这一点,即使logging
标头不存在,它仍然可以编译?
现在我可以考虑在预处理过程中使用宏使所有与日志记录相关的部分都有条件。类似:
#ifdef USE_LOGGING
#include "../logging/logger.h"
#endif
class GerometryBase {
//...
void do_something() { if (logger) _log("doing something"); }
#ifdef USE_LOGGING
void _log(const std::string& s) {//...}
Logger* logger = nullptr;
#else
void _log(const std::string& s) {// do nothing}
void* logger = nullptr;
#endif
}; // class
有更好/更干净的方法吗?此类设计是否有推荐的指导方针或最佳实践?
=================================================
更新
下面是一个使用函数指针(基于rioki的想法)的示例实现,它确实有助于解耦对象:
目标h
#ifndef MYOBJ_H_
#define MYOBJ_H_
#include <iostream>
class MyObj {
public:
MyObj() { std::cout << "constructing MyObj" << std::endl; }
void setLogger( void (*p)(const char*, int) ) {
logger = p;
}
void do_somthing() {
if (logger) {
logger("this is a debug message", 1);
}
}
private:
void (*logger)(const char*, int ) = nullptr;
};
#endif
logger.h
#ifndef LOGGER_H
#define LOGGER_H
void logger(const char* , int);
#endif
记录器.cpp
#include <iostream>
#include "logger.h"
void logger(const char* str, int lvl) {
std::cout << "level " << lvl << " " << str << std::endl;
}
main.cpp
#include "logger.h"
#include "obj.h"
int main() {
MyObj obj;
obj.setLogger(logger);
obj.do_somthing();
return 0;
}
输出:
constructing MyObj
level 1 this is a debug message
对于"这样它们甚至可以独立编译",您可以将类声明为一个类,
class Logger;
然后,您可以随意将它用于正式的结果和参数类型,但由于编译器不知道它的大小或它的成员,因此您无法使用它,例如在函数实现中。
但是,在另一个头中包含一个头和仅在实现文件中包含它之间有很大的区别:后者对总构建时间贡献一次,而前者可能贡献多次,每个翻译单元贡献一次。
另一方面,如果您只做头模块,那么就无法包含所有相关的代码。
您的几何模块中真的需要记录器吗?总是问"我真的需要B中的A吗?"来确定两个模块的耦合是否合理。
有多种方法可以消除两个模块之间的依赖关系。
几何类真的需要记录器吗?不,它只记录致命错误
然后抛出一个异常,以防出现致命错误,捕获它并将其记录在更高级别的代码中。这使得几何结构完全独立于记录器或任何其他模块。
几何类真的需要记录器吗?也许,我写了一堆诊断信息
不如为记录器定义一个完全虚拟的接口(抽象基类)。这只会引入对标头的依赖关系。您只需要接口的头,而不需要整个模块。如果指向记录器的指针为NULL,则不要记录任何内容。
您可以定义任何使用ostream
编写诊断信息的函数吗。像这样,您可以捕获所有信息并将其记录到更高级别。这允许您传递字符串流或cout,并增加您的灵活性。您已经拥有的唯一依赖项是C++标准库。
不如将setLogger定义为不接受对象,而是接受std::function
。例如:
class GerometryBase
{
public:
void setLogger(std::function<void (const std::string&)> value)
{
logger = value;
}
private:
std::function<void (const std::string&)> logger;
void log(const std::string& msg)
{
if (logger)
{
logger(msg);
}
}
}
将记录器绑定到几何类:
Logger logger;
Box box;
box.setLogger([&] (const std::string& msg) {
logger.log(msg);
});
有很多方法可以减少模块之间的耦合。你只需要考虑一下。浏览标准库是我最喜欢的方式,这是有充分理由的。自从C++11引入lambdas以来,我的模块中的耦合显著减少。
您可以在公共头文件中声明接口,并在运行时解析具体的依赖关系。在您的示例中,几何模块包括定义抽象类Logger
的#include "common/logger.hpp"
。几何体库的用户可以决定是使用记录器库中的记录器实现,还是实现自己的记录器实现。
- 设计一个只能由特定类实例化的类(如果可能的话,通过make_unique)
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 询问在设计我的手臂模拟器功能表示格式1
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 资源管理设计模式
- 多态杆件变量 - 类设计
- 如何设计具有不同类型的通知和观察器的观察者模式?
- 如何使用要传递给 mt19937 的可选随机种子参数设计函数
- 设计将引用元素移动到开头的数据结构.C++
- С++ wxWidgets:代码架构,设计原则和模式
- 用于在回调中调用解析器的设计模式
- 在设计 SDK 时,我是否应该在 C++ 头文件中完全隐藏内部类?
- 需要为 C++ 中的以下问题设计递归算法
- 使用 SFINAE 设计模板方法
- C++,您能否设计一种数据结构,将指针保存在连续内存中并且不会使它们失效?
- 设计帮助 - 为不同类型的消息处理通用接口的设计模式
- C++模板编程设计问题 - 根据输入文件返回不同的类型
- 在这种情况下我应该使用哪种设计模式
- C++中物体改变识别的设计模式?