我正在使用 C 20 概念通過靜態多型性制作依賴注入系統,所以我有這個例子
我有一個介面合同Logger:
template <typename TLogger>
concept ILogger = requires(TLogger engine) {
{ engine.LogInfo(std::declval<std::string>()) } -> std::same_as<void>;
{ engine.LogError(std::declval<int>(), std::declval<std::string>()) } -> std::same_as<void>;
};
該介面的特定實作具有 2 個 ctor,一個默認值和一個非默認值:
class MyLoggerImplementation
{
public:
MyLoggerImplementation() : x(0)
{
}
MyLoggerImplementation(int code) //non default constructor
{
x = code;
}
//Interface methods
public:
void LogInfo(std::string errorMessage)
{
std::cout << std::format("Info: {}!\n", errorMessage);
}
void LogError(int errorCode, std::string errorMessage)
{
std::cout << std::format("Error: {}! with code: {}\n", errorMessage, errorCode);
}
// private members
private:
int x;
};
然后我DatabaseAccessor對 a 有依賴性Logger:
template<ILogger Logger>
class DatabaseAccessor
{
public:
DatabaseAccessor() { }
public:
bool QueryAll()
{
logger.LogInfo("querying all data");
//...
return true;
}
// Injected services
private:
Logger logger;
};
然后,當我嘗試實體化 aDatabaseAccessor并注入Logger依賴項時MyLoggerImplementation,它將呼叫默認建構式:
int main()
{
DatabaseAccessor<MyLoggerImplementation> db; // this will instantiate with the default constructor
// DatabaseAccessor<MyLoggerImplementation(10)> db; // ???
db.QueryAll();
}
如何MyLoggerImplementation使用該非默認建構式進行實體化?
uj5u.com熱心網友回復:
您可以將記錄器的實體作為引數:
template <ILogger L>
class DatabaseAccessor
{
L logger;
public:
explicit DatabaseAccessor(L logger) : logger(std::move(logger)) { }
};
如果您需要比這更多的東西(比如您的記錄器不是可移動構造的),那么L您可以使用回傳 an 的函式,而不是使用 an L:
template <class F, class T>
concept FactoryFor = std::invocable<F> and std::same_as<std::invoke_result_t<F>, T>;
template <ILogger L>
class DatabaseAccessor
{
L logger;
public:
template <FactoryFor<L> F>
explicit DatabaseAccessor(F factory) : logger(f()) { }
};
與僅將引數轉發到記錄器相比,這種方法具有以下優勢:
- 當您添加另一個需要初始化的成員時,它作業得很好
- 它允許使用支撐初始化串列來初始化記錄器
- 在呼叫站點上更清楚(考慮
DatabaseAccessor<OstreamLogger> da(std::cout);- 訪問器在做什么coutvsDatabaseAccessor da(OstreamLogger(std::cout));)
請注意:
template <typename TLogger>
concept ILogger = requires(TLogger engine) {
{ engine.LogInfo(std::declval<std::string>()) } -> std::same_as<void>;
{ engine.LogError(std::declval<int>(), std::declval<std::string>()) } -> std::same_as<void>;
};
真的可能是:
template <typename TLogger>
concept ILogger = requires (TLogger engine, int i, std::string s) {
{ engine.LogInfo(s) } -> std::same_as<void>;
{ engine.LogError(i, s) } -> std::same_as<void>;
};
兩者并不完全相同(我在傳遞左值時傳遞左值),但我懷疑這種區別在您的用例中是否重要。
uj5u.com熱心網友回復:
您必須將所需的引數傳遞給DatabaseAccessor建構式,因此其初始化串列然后可以將引數傳遞給Logger的建構式,例如:
template<ILogger Logger>
class DatabaseAccessor
{
public:
template<typename... Params>
DatabaseAccessor(Params&&... params) : logger(std::forward<Params>(params)...) { }
...
private:
Logger logger;
};
DatabaseAccessor<MyLoggerImplementation> db(12345);
uj5u.com熱心網友回復:
您可以添加一個轉發建構式DatabaseAccessor,它將傳遞引數以構造包裝的日志物件。那看起來像
template<ILogger Logger>
class DatabaseAccessor
{
public:
DatabaseAccessor() { }
template <typename... Ts, std::enable_if_t<std::is_constructible_v<Logger, Ts...>, bool> = true>
DatabaseAccessor(Ts&&... ts) : logger(std::forward<Ts>(ts)...) { }
public:
bool QueryAll()
{
logger.LogInfo("querying all data");
//...
return true;
}
// Injected services
private:
Logger logger;
};
DatabaseAccessor<MyLoggerImplementation> db(10);
該std::enable_if_t<std::is_constructible_v<Logger, Ts...>, bool> = true部分是一種SFINAE 技術Logger,僅當型別可以通過傳入的引數構造時才允許呼叫此建構式。這是必需的,否則當您想要進行復制或移動時,可能會呼叫此模板。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/508180.html
