【问题标题】:Modular C++ Design模块化 C++ 设计
【发布时间】:2014-01-26 04:39:44
【问题描述】:

我正在设计一个包含多个模块的工具包。我试图使模块尽可能独立,因此它们甚至可以独立编译(例如作为库)。

其中一个模块是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 的想法),它确实有助于解耦对象:

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

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

【问题讨论】:

  • 你真的应该像 rioki 建议的那样使用 std::function 而不是函数指针。它使您的代码更易于阅读,并允许您清楚地表达您的意图。

标签: c++ modular


【解决方案1】:

对于“因此它们甚至可以独立编译”,您可以将类声明为类,

class Logger;

然后你可以随意将它用于正式的结果和参数类型,但由于编译器不知道它的大小或它的成员,你不能用它做任何事情,例如在函数实现中。

但是在另一个头文件中包含一个头文件和只在一个实现文件中包含一个头文件之间有很大的区别:后者对总构建时间有一次贡献,而前者可能会贡献很多次,每个翻译单元一次。

另一方面,如果您正在制作仅标头模块,那么就无法包含所有相关代码。

【讨论】:

  • 前向声明很好,但正如您所说,不允许您使用该类的功能。感谢您提供包含 btw 的提示。
【解决方案2】:

您真的需要几何模块中的记录器吗?总是问“我真的需要B中的A吗?”确定两个模块的耦合是否合理。

有多种方法可以消除两个模块之间的依赖关系。

几何类真的需要记录器吗?不,它只记录致命错误。

然后抛出异常以防出现致命错误,捕获它并将其记录在更高级别的代码中。这使得几何完全独立于记录器或任何其他模块。

几何类真的需要记录器吗?也许,我写了一堆诊断信息。

您如何为记录器定义一个完全虚拟的接口(抽象基类)。这只会引入对标头的依赖。您只需要接口的标题,而不需要整个模块。如果指向记录器的指针为 NULL,则不要记录任何内容。

您如何定义任何写入诊断信息的函数,采用ostream。像这样,您可以捕获所有信息并将其记录在更高级别。这允许您传递 stringstream 或 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 引入了 lambda,我的模块中的耦合显着降低了。

【讨论】:

  • 使用函数的想法很有趣。我能够使用函数指针来实现这一点。我会更新我的问题。
【解决方案3】:

您可以在公共头文件中声明接口并在运行时解析具体的依赖关系。在您的示例中,几何模块包括#include "common/logger.hpp",它定义了一个抽象类Logger。几何库的用户可以决定是使用您的 logger 库中的 Logger 实现还是实现他自己的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-25
    • 1970-01-01
    • 2015-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多