C++中構(gòu)造對(duì)象的方式

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ù)名字綁定等功能

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容