C++中構(gòu)造對(duì)象的方式
對(duì)于有參數(shù)的構(gòu)造函數(shù)的類,該實(shí)現(xiàn)在構(gòu)造改對(duì)象時(shí)傳遞默認(rèn)值來(lái)構(gòu)造。當(dāng)然用戶也可以指定(綁定)某個(gè)參數(shù)的值。 實(shí)現(xiàn)思路參考boost-ext/di的實(shí)現(xiàn)。
來(lái)看下例子:
struct Member{
? ? int x = 10;
};
struct Member1 {
? ? int x = 11;
};
class Example1{
public:
Example1(Member x, Member1 x1) {
? ? std::cout << x.x << std::endl; // 10
? ? ? ? std::cout << x1.x << std::endl;? // 11
? ? }
};
int main() {
auto e1 = farrago::ObjectCreator<>().template Create<Example1>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
例子比較簡(jiǎn)單,構(gòu)造一個(gè)ObjectCreator對(duì)象,并調(diào)用他的Create來(lái)創(chuàng)建一個(gè)Example1的對(duì)象,
因?yàn)槭褂肙bjectCreator來(lái)構(gòu)造,所以不需要傳遞參數(shù),它會(huì)自動(dòng)構(gòu)造。
這樣做的好處是,當(dāng)你構(gòu)造一個(gè)對(duì)象時(shí),可以無(wú)需考慮這個(gè)對(duì)象的構(gòu)造函數(shù)是幾個(gè)參數(shù)或類型,當(dāng)想要增加參數(shù)時(shí)則無(wú)需修改代碼,當(dāng)然指定參數(shù)的話除外。這種用法也被稱為依賴注入
構(gòu)思主體實(shí)現(xiàn)
看起來(lái)還蠻酷炫,那主要還是看如何做到的?
先來(lái)說(shuō)下主體想法,首先最重要的當(dāng)然是ObjectCreator這個(gè)類中如何知道要構(gòu)造的對(duì)象的構(gòu)造函數(shù)的參數(shù)類型是什么呢,知道參數(shù)類型才能構(gòu)造一個(gè)參數(shù)傳遞,同時(shí)參數(shù)的也同樣需要ObjectCreator來(lái)構(gòu)造,依次遞歸下去。
上邊說(shuō)到了兩個(gè)問(wèn)題要解決,第一個(gè)就是如何識(shí)別構(gòu)造函數(shù)的參數(shù)類型,第二個(gè)是針對(duì)構(gòu)造函數(shù)參數(shù)也需要構(gòu)造的情況下,如果遞歸構(gòu)造?
識(shí)別構(gòu)造函數(shù)參數(shù)類型
我們使用AnyType的形式來(lái)識(shí)別出來(lái)構(gòu)造函數(shù)的參數(shù),舉個(gè)簡(jiǎn)單的例子:
struct AnyType {
? ? template<typename T>
? ? operator T() {
? ? ? ? return T{};
? ? }
};
struct Member {};
struct Example {
? ? Example(Member m, int) {
? ? }
};
int main() {
? ? Example(AnyType(), 2);
? ? return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
通過(guò)調(diào)用AnyType()可以匹配至任意類型,然后在構(gòu)造Example編譯器會(huì)去找相應(yīng)的類型來(lái)構(gòu)造。
大家可能發(fā)現(xiàn)我使用的是多個(gè)參數(shù)來(lái)舉例AnyType,如果參數(shù)是一個(gè)使用AnyType會(huì)有沖突,因?yàn)榭截悩?gòu)造函數(shù)也是一個(gè)參數(shù),所以編譯器會(huì)識(shí)別沖突,這個(gè)問(wèn)題我們后邊也是需要處理的。
class Example {
public:
? ? Example(Member m) {
? ? ? ? std::cout << m.x << std::endl;
? ? }
};
int main() {
? ? Example e(AnyType{});
? ? return 0;
}
// -------- 以下報(bào)錯(cuò)
note: candidate: 'Example::Example(Member)'
|? ? Example(Member m) {
|? ? ^~~~~~~
: note: candidate: 'constexpr Example::Example(const Example&)'
class Example {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
遞歸構(gòu)造構(gòu)造函數(shù)的參數(shù)
因?yàn)闃?gòu)造函數(shù)的參數(shù)可能是一個(gè)類對(duì)象,這個(gè)對(duì)象的構(gòu)造函數(shù)參數(shù)又是其他類對(duì)象,我們識(shí)別類型后繼續(xù)調(diào)用函數(shù)來(lái)構(gòu)造這個(gè)對(duì)象,以此類推。
保存綁定參數(shù)
當(dāng)然使用過(guò)程也不全部是使用默認(rèn)構(gòu)造,可能也需要傳遞特定參數(shù)與構(gòu)造函數(shù)的參數(shù)進(jìn)行綁定,但是構(gòu)造函數(shù)的參數(shù)類型又是多樣的。這里我采用了tuple先來(lái)保存,倘若識(shí)別出來(lái)的類型和保存的數(shù)據(jù)類型是一致的,則不去構(gòu)造而是直接傳遞該數(shù)據(jù)給構(gòu)造函數(shù)。
代碼實(shí)現(xiàn)
那沿著上邊的思路就開(kāi)始寫(xiě)代碼,肯定有一個(gè)AnyType的類及Objectcreator的類。ObjectCreator用來(lái)構(gòu)造對(duì)象返回,會(huì)只用AnyType類來(lái)識(shí)別類型。
ObjectCreator
大概看下具體的實(shí)現(xiàn):
template<typename... Args>
class ObjectCreator {
public:
? ? template<typename... Ts>
? ? explicit ObjectCreator(Ts&&... args) :
? ? dependency_(std::forward<Ts>(args)...) {}
// ...
private:
? ? std::tuple<const Args& ...> dependency_;
};
1
2
3
4
5
6
7
8
9
10
11
12
我們使用tuple保存要綁定的參數(shù)時(shí),數(shù)據(jù)的保存就得進(jìn)行拷貝,我們這里為了避免拷貝,tuple中的類型是const左引用,這樣就得用戶自己來(lái)維護(hù)要綁定的參數(shù)的生命周期。
Args是要綁定的參數(shù)類型,構(gòu)造函數(shù)中為了避免拷貝使用完美轉(zhuǎn)發(fā)來(lái)實(shí)現(xiàn)。dependency_就是保存綁定參數(shù)的數(shù)據(jù)結(jié)構(gòu)
template<typename... Args>
class ObjectCreator {
// ...
template<typename T>
T Create() {
? ? if constexpr ((std::is_same<T, Args>::value || ...)) {
? ? ? ? return std::get<const T&>(dependency_);
? ? }
? ? else if constexpr (std::is_default_constructible_v<T>) {
? ? ? ? return T{};
? ? }
? ? else if constexpr (std::is_constructible<T,
? ? ? AnyFirstRefType<ObjectCreator, T, FarragoNull, Args...>>::value) {
? ? ? ? return T{AnyFirstRefType<ObjectCreator, T, FarragoNull, Args...>{this}};
? ? }
? ? else if constexpr (std::is_constructible<T,
? ? ? AnyFirstType<ObjectCreator, T, FarragoNull, Args...>>::value) {
? ? ? ? return T{AnyFirstType<ObjectCreator, T, FarragoNull, Args...>{this}};
? ? }
? ? else {
? ? ? ? return CreateMoreParamObject<T>(std::make_index_sequence<10>{});
? ? }
}
// ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
這里就是create函數(shù)了:
首先判斷是不是要?jiǎng)?chuàng)建的類對(duì)象已經(jīng)綁定了,如果綁定了則直接從tuple中取出返回。
沒(méi)有綁定的話然后再判斷默認(rèn)構(gòu)造(即可以無(wú)參構(gòu)造)是否可以構(gòu)造,可以的話返回一個(gè)空對(duì)象。
然后進(jìn)行判斷是不是一個(gè)參數(shù)構(gòu)造函數(shù)的判斷,一個(gè)參數(shù)這里分成了兩種,是引用類型或者非引用類型。這樣做是因?yàn)?,T和T&在識(shí)別是會(huì)沖突,所以分開(kāi)處理。舉例說(shuō)明:
struct AnyType {
? ? template<typename T>
? ? operator T() {
? ? ? ? return T{};
? ? }
? ? template<typename T>
? ? operator T&() {
? ? ? ? return T{};
? ? }
};
class Example {
public:
? ? Example(Member m, int) {
? ? ? ? std::cout << m.x << std::endl;
? ? }
};
Example e(AnyType{}, 7);
// 報(bào)錯(cuò)如下:
error: conversion from 'AnyType' to 'Member' is ambiguous
Example e(AnyType{}, 7);
^~~~~~~~~
candidate: 'AnyType::operator T() [with T = Member]'
operator T() {
^~~~~~~~
note: candidate: 'AnyType::operator T&() [with T = Member]'
operator T&() {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
最后是多個(gè)參數(shù)的構(gòu)造函數(shù)進(jìn)行構(gòu)造,一個(gè)參數(shù)和多個(gè)參數(shù)分開(kāi)的原因是,一個(gè)參數(shù)需要對(duì)拷貝構(gòu)造函數(shù)及單參的構(gòu)造函數(shù)沖突的情況進(jìn)行處理,我們傳遞了1~10的整數(shù)序列作為參數(shù)給CreateMoreParamObject函數(shù),這里表示目前該實(shí)現(xiàn)最多只能支持10個(gè)參數(shù)的構(gòu)造函數(shù)。
繼續(xù)看下多參的構(gòu)造:
template<typename T, std::size_t... Ns>
T CreateMoreParamObject(const std::index_sequence<Ns...>&) {
? ? if constexpr (std::is_constructible_v<T,
? ? ? At<AnyRefType<ObjectCreator, FarragoNull, Args...>, Ns>...>) {
? ? ? ? return T{At<AnyRefType<ObjectCreator, FarragoNull, Args...>, Ns>{this}...};
? ? }
? ? else {
? ? ? ? return CreateMoreParamObject<T>(std::make_index_sequence<sizeof...(Ns) - 1>{});
? ? }
}
1
2
3
4
5
6
7
8
9
10
首先判斷是否可以由多個(gè)AnyRefType類型來(lái)構(gòu)造出來(lái),如果可以的話,直接構(gòu)造對(duì)象,不可以的話就需要將參數(shù)個(gè)數(shù)減少重新匹配。
AnyType
然后我們來(lái)觀察AnyType如何編寫(xiě),先來(lái)看下AnyFirstType的情況。
為了避免和拷貝構(gòu)造函數(shù)沖突,簡(jiǎn)單做一下優(yōu)化:
struct AnyFirstType {
? ? template <typename T,
? ? typename = std::enable_if_t<!std::is_same_v<Src, T>>>
? ? constexpr operator T() {
? ? ? ? return creator_->template Create<T>();
? ? }
};
1
2
3
4
5
6
7
我們使用SFINAE來(lái)將拷貝構(gòu)造函數(shù)排除在外,使用AnyFirstType識(shí)別時(shí)參數(shù)類型時(shí),需要將要構(gòu)造的類當(dāng)作模版參數(shù)傳遞給Src,讓T與Src不一樣進(jìn)而告訴編譯器要調(diào)用的不是拷貝構(gòu)造函數(shù)而是其他的函數(shù)。
creator_就是ObjectCreator對(duì)象,對(duì)參數(shù)的構(gòu)造對(duì)Create函數(shù)進(jìn)行遞歸調(diào)用。
多個(gè)參數(shù)也是類似實(shí)現(xiàn),只是不需要額外判斷是不是拷貝構(gòu)造函數(shù)的參數(shù)。
不過(guò)還有一個(gè)點(diǎn)可能需要注意就是,如果構(gòu)造函數(shù)的類型是引用類型,在和綁定參數(shù)匹配情況下會(huì)多一次拷貝,所以我們也還是區(qū)分開(kāi)來(lái)。
template <typename Creator, typename Src, typename... Args>
struct AnyFirstRefType {
? ? template <typename T,
? typename = std::enable_if_t<!std::is_same_v<Src, std::decay_t<T>>>,
? ? typename = std::enable_if_t<(std::is_same<std::decay_t<T>, Args>::value || ...)>>
? ? constexpr operator T& () {
? ? ? ? return const_cast<T&>(creator_->template GetDependency<T>());
? ? }
? ? template <typename T,
? ? typename = std::enable_if_t<!std::is_same_v<Src, std::decay_t<T>>>,
? typename = std::enable_if_t<(std::is_same<std::decay_t<T>, Args>::value || ...)>>
? ? constexpr operator T &&() {
? ? ? ? return static_cast<T&&>(const_cast<T&>(creator_->template GetDependency<T>()));
? ? }
? ? Creator* creator_ = nullptr;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在和綁定參數(shù)匹配并且傳遞引用的情況下,我們單獨(dú)實(shí)現(xiàn),直接返回不再調(diào)用Creator的Create函數(shù),并且做一下強(qiáng)制轉(zhuǎn)化。多參數(shù)的類型識(shí)別也是類似。
總結(jié)
本文展示了一種對(duì)象構(gòu)造的實(shí)現(xiàn),使用AnyType的思路實(shí)現(xiàn),中間也處理很多的問(wèn)題。對(duì)于無(wú)需綁定(或部分綁定)構(gòu)造函數(shù)參數(shù)的對(duì)象的構(gòu)造,可擴(kuò)展性及可維護(hù)性都有很好提升。當(dāng)然該實(shí)現(xiàn)目前也尚不完備,目前只是類型綁定,也可以實(shí)現(xiàn)參數(shù)名字綁定等功能