[英]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的想法)的示例實現,它有助於解耦對象:
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
對於“所以它們甚至可以獨立編譯”,你可以將類聲明為一個類,
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引入了lambda,因此模塊中的耦合顯着減少。
您可以在公共頭文件中聲明接口,並在運行時解析具體的依賴項。 在您的示例中,幾何模塊包括#include "common/logger.hpp"
,它定義了一個抽象類Logger
。 幾何lib的用戶可以決定他是否使用logger lib中的Logger實現或實現自己的實現。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.