国产日韩欧美一区二区三区三州_亚洲少妇熟女av_久久久久亚洲av国产精品_波多野结衣网站一区二区_亚洲欧美色片在线91_国产亚洲精品精品国产优播av_日本一区二区三区波多野结衣 _久久国产av不卡

?

基于C++17的泛型函數(shù)容器實(shí)現(xiàn)方法研究

2019-07-03 02:31閔軍羅泓
軟件工程 2019年5期
關(guān)鍵詞:型函數(shù)調(diào)用容器

閔軍 羅泓

摘? 要:泛型函數(shù)容器的使用可以解耦對象之間的調(diào)用關(guān)系,有利于實(shí)現(xiàn)高內(nèi)聚、低耦合的軟件設(shè)計(jì)原則。C++標(biāo)準(zhǔn)庫中并沒有這樣的容器,用C++舊標(biāo)準(zhǔn)實(shí)現(xiàn)也很困難、很低效。C++1x等新標(biāo)準(zhǔn)發(fā)布后,出現(xiàn)了一些更好的實(shí)現(xiàn)方式。本文將在已有設(shè)計(jì)的基礎(chǔ)之上,基于C++17新標(biāo)準(zhǔn),利用if constexpr、fold expression、std::invoke等新技術(shù),提供一種泛型函數(shù)容器的實(shí)現(xiàn)方式。測試表明該實(shí)現(xiàn)方式簡潔高效,解決了重載函數(shù)和某些特殊函數(shù)的注冊調(diào)用問題,可以顯著降低耦合性、提高代碼復(fù)用性。

關(guān)鍵詞:C++17;泛型;函數(shù)容器;高內(nèi)聚;低耦合

中圖分類號:TP311.1? ? ?文獻(xiàn)標(biāo)識碼:A

Abstract:The application of generic function containers can decouple the calling relationships between objects,conducive to the realization of high cohesion and low coupling software design principles.There is no such container in the C++ standard library,and it is very difficult and inefficient to implement with the old C++ standard.The release of new standards,such as C++1x,has brought some better implementation methods.This paper provides a generic function container implementation method based on the existing design and the new C++17 standard,via some new technologies such as if constexpr,fold expression,and std::invoke.Test results show that the simple and efficient implementation method effectively solves the problem of registration and calling of overloaded functions and some special functions,significantly reducing coupling and improving code reusability.

Keywords:C++17;generic;function container;high aggregation;low coupling

1? ?引言(Introduction)

高內(nèi)聚、低耦合是軟件設(shè)計(jì)的基本原則,泛型函數(shù)容器的使用可以解耦對象之間的調(diào)用關(guān)系,有利于實(shí)現(xiàn)軟件設(shè)計(jì)的這一基本原則[1]。作為一種萬能函數(shù)注冊器,泛型函數(shù)容器可以將任意類型的函數(shù)用一個(gè)key進(jìn)行注冊以供其他程序調(diào)用,可以注冊普通函數(shù)、函數(shù)模板、成員函數(shù)、函數(shù)對象、lambda表達(dá)式、重載函數(shù)和某些特殊函數(shù)等。當(dāng)全局函數(shù)或?qū)ο笾g存在交互調(diào)用需求時(shí),比如需要相互調(diào)用對方成員函數(shù)或者不適合關(guān)聯(lián)的無關(guān)對象之間需要調(diào)用其他對象的成員函數(shù)或者全局函數(shù)需要調(diào)用成員函數(shù)等類似需求時(shí),我們便可以將需要被調(diào)用的函數(shù)用一個(gè)key注冊起來以供其他實(shí)體調(diào)用。函數(shù)的調(diào)用者不必知道被調(diào)用者,二者都依賴于中間的泛型函數(shù)容器,借此便可以解耦對象之間的調(diào)用關(guān)系[2]。

C++標(biāo)準(zhǔn)庫中并沒有現(xiàn)存的泛型函數(shù)容器,用C++舊標(biāo)準(zhǔn)實(shí)現(xiàn)也很困難、很低效[3]。C++11、C++14、C++17等新標(biāo)準(zhǔn)發(fā)布后,出現(xiàn)了一些更好的實(shí)現(xiàn)方式。本文便是在已有設(shè)計(jì)的基礎(chǔ)之上,基于C++17新標(biāo)準(zhǔn),利用if constexpr、fold expression、std::invoke等新技術(shù),提供一種泛型函數(shù)容器的實(shí)現(xiàn)方式[4,5]。

2? 泛型函數(shù)容器的結(jié)構(gòu)設(shè)計(jì)(Design of genericfunction container structure)

泛型函數(shù)容器的基本結(jié)構(gòu)與C++ STL中的容器類似。在把各種不同類型的函數(shù)存入容器時(shí)必須轉(zhuǎn)換為統(tǒng)一的數(shù)據(jù)結(jié)構(gòu),通過一個(gè)key進(jìn)行注冊,此后用戶便可以借助這個(gè)key和必要信息提取已注冊的記錄來執(zhí)行該函數(shù),從而實(shí)現(xiàn)泛型函數(shù)容器的基本功能。

2.1? ?泛型函數(shù)容器的結(jié)構(gòu)圖

2.2? ?函數(shù)存取結(jié)構(gòu)的設(shè)計(jì)

由于泛型函數(shù)容器需要存取各種不同類型的函數(shù),具體包括普通函數(shù)、函數(shù)模板、成員函數(shù)、函數(shù)對象、lambda表達(dá)式、重載函數(shù)和某些特殊函數(shù)等,所以需要抽象出能夠區(qū)分各種不同函數(shù)的標(biāo)志特征:函數(shù)簽名(Function Signature)。類似于用簽名可以識別不同人一樣,通過函數(shù)簽名便可以識別不同的函數(shù)[6]。從便于注冊和提取的角度,本項(xiàng)目主要關(guān)注函數(shù)簽名的四個(gè)部分:函數(shù)名f、函數(shù)所屬對象的指針pObj、參數(shù)包指針pArgsList、返回值指針pRet。若是成員函數(shù),pObj必須明確賦值,否則就必須賦值為空指針nullptr。

然后設(shè)計(jì)一個(gè)函數(shù)封裝類invoker,用來保存注冊函數(shù)的函數(shù)簽名。Fun是注冊函數(shù)的具體類型(包含函數(shù)簽名),每注冊一個(gè)函數(shù)就創(chuàng)建一個(gè)以模板參數(shù)Fun區(qū)分的該類對象,每個(gè)不同對象保存了注冊函數(shù)的函數(shù)簽名(包含四個(gè)部分),調(diào)用時(shí)便可以從中提取函數(shù)簽名來執(zhí)行該函數(shù)。

2.3? ?函數(shù)注冊和提取部分的設(shè)計(jì)

函數(shù)注冊時(shí)需要完整保存函數(shù)簽名各個(gè)部分的信息,不過調(diào)用時(shí)并不需要用戶提供完整的函數(shù)簽名。為了簡化調(diào)用方式,函數(shù)調(diào)用時(shí)用戶只需輸入函數(shù)參數(shù)列表和返回值類型即可,若是無返回值函數(shù)則只需輸入函數(shù)參數(shù)列表(可以理解為返回值為void類型)。按照以上設(shè)計(jì)思路,注冊函數(shù)時(shí)需要完整保存函數(shù)簽名的四個(gè)部分,調(diào)用函數(shù)時(shí)用戶只需輸入兩個(gè)參數(shù)即可。因此,設(shè)計(jì)注冊函數(shù)reg_fun時(shí)需要有四個(gè)參數(shù),設(shè)計(jì)調(diào)用函數(shù)call時(shí)只需有兩個(gè)參數(shù)即可。具體實(shí)現(xiàn)可參見后面完整代碼中的reg_fun、call。

3? 關(guān)鍵數(shù)據(jù)成員m_mapInvoker的設(shè)計(jì)(Design of the key data member m_mapInvoker)

3.1? ?關(guān)鍵數(shù)據(jù)成員m_mapInvoker保存的是key與函數(shù)的成對記錄

我們可以使用std::map容器來保存泛型函數(shù)容器的注冊數(shù)據(jù)。在本項(xiàng)目中,關(guān)鍵數(shù)據(jù)成員m_mapInvoker便是用于保存key與函數(shù)成對記錄的std::map容器。m_mapInvoker的key字段為std::string類型,是用戶指定名稱或者返回值及參數(shù)類型名稱的累加字符串;data字段則是std::function類模版封裝的特殊函數(shù)類型[7]。

若用戶注冊時(shí)指定了注冊名稱便以此為該條記錄的key,此時(shí)的key用std::string類型,不至于產(chǎn)生混淆。不過在調(diào)用該注冊函數(shù)時(shí),若直接使用std::string類型的key,便可能與參數(shù)類型產(chǎn)生混淆,因?yàn)樽院瘮?shù)的第一個(gè)參數(shù)也可能是std::string類型。所以,在調(diào)用該注冊函數(shù)時(shí),必須使用key_fun類型的key,才能避免可能發(fā)生的混淆。fun_key是只含一個(gè)數(shù)據(jù)成員std::string key的簡單封裝類型,其主要作用就是在調(diào)用時(shí)避免與函數(shù)參數(shù)發(fā)生混淆。

若用戶注冊時(shí)未指定注冊名稱,便使用返回值及參數(shù)類型名稱的累加字符串作為該條記錄的key,提取時(shí)也會(huì)自動(dòng)生成返回值及參數(shù)類型名稱的累加字符串作為key來查詢調(diào)用該注冊函數(shù)。這樣的設(shè)計(jì),同時(shí)也解決了參數(shù)列表相同、返回值不同的多個(gè)函數(shù)的注冊調(diào)用問題。

m_mapInvoker的data字段用于保存函數(shù)簽名的四個(gè)部分,它是以std::function類模版封裝的特殊函數(shù)void(void*,void*)。該封裝函數(shù)無返回值,第一個(gè)參數(shù)為注冊函數(shù)的參數(shù)包指針,第二個(gè)參數(shù)為注冊函數(shù)的返回值指針。

具體實(shí)現(xiàn)可以參見完整代碼中的fun_key類(其結(jié)構(gòu)參見圖2)、call_impl、get_key_from_fun_args等函數(shù),以及后面的測試代碼。

3.2? ?封裝函數(shù)的原始模型

關(guān)鍵數(shù)據(jù)成員m_mapInvoker的data字段中保存的并非單純的封裝函數(shù),而是通過std::bind綁定到原始函數(shù)模型上的間接函數(shù)[8]。封裝函數(shù)的原始模型是invoker::apply(…),F(xiàn)un是注冊函數(shù)的函數(shù)類型(包含函數(shù)簽名),Object是注冊函數(shù)所屬的對象類型(若是非成員函數(shù)則為void)。原始函數(shù)invoker::apply(…)有四個(gè)參數(shù),分別用于關(guān)聯(lián)注冊函數(shù)類型(函數(shù)簽名)的四個(gè)部分:函數(shù)名f、成員函數(shù)的對象指針pObj、參數(shù)包指針_1、返回值指針_2。_1、_2為C++11新標(biāo)準(zhǔn)的占位符[9],是用戶在提取記錄調(diào)用注冊函數(shù)時(shí)需要輸入的參數(shù),若注冊函數(shù)無返回值,則只需輸入第一個(gè)參數(shù)即可。具體可以參見后面的完整代碼。

3.3? ?解決重載函數(shù)和某些特殊函數(shù)的注冊調(diào)用問題

在以上設(shè)計(jì)的泛型函數(shù)容器中,若直接注冊存在兩個(gè)以上實(shí)例的重載函數(shù),編譯時(shí)就會(huì)報(bào)錯(cuò)。解決該問題的思路很簡單,就是針對重載函數(shù)的多個(gè)實(shí)例對應(yīng)地定義多個(gè)不同名稱和類型的函數(shù)指針、并將重載函數(shù)賦值給它們,這就相當(dāng)于將多個(gè)重載函數(shù)的實(shí)例轉(zhuǎn)換成為多個(gè)不同名稱和類型的新函數(shù)指針。使用這些新的函數(shù)指針便能在泛型函數(shù)容器中成功進(jìn)行注冊和調(diào)用。另外,也可以用lambda表達(dá)式來消除重載函數(shù)的二義性[10]。對某些特殊函數(shù)的處理也類似,包括特殊函數(shù)1:參數(shù)列表相同、返回值不同的多個(gè)函數(shù)的注冊和調(diào)用,這在本項(xiàng)目設(shè)計(jì)中已經(jīng)解決;特殊函數(shù)2:參數(shù)列表相同、返回值相同的多個(gè)函數(shù)的注冊和調(diào)用,可以用lambda表達(dá)式封裝該函數(shù),增加一個(gè)參數(shù)即可。具體請參見后面的測試代碼。

4? ?利用C++17新技術(shù)優(yōu)化代碼設(shè)計(jì)(Optimize code design with C++17 new technology)在本項(xiàng)目中,使用了C++17的if constexpr[11]新技術(shù)在編譯期進(jìn)行判斷,去除enable_if_t,合并許多功能類似的函數(shù)。包括:合并非成員函數(shù)、成員函數(shù)注冊的兩種reg_fun函數(shù);合并有返回值、無返回值的兩種call函數(shù);合并Key與fun_key類型相同和不同的兩種call_impl、get_key_from_fun_args函數(shù)等。

在C++17之前,我們經(jīng)常用逗號表達(dá)式和std::initializer_list將變參依次傳入一個(gè)函數(shù)。用C++17的fold expression折疊表達(dá)式代替initializer_list,就要簡潔得多[12]。本項(xiàng)目便使用了C++17的這一新技術(shù)簡化代碼設(shè)計(jì)。

利用C++17的invoke調(diào)用器,可以合并非成員函數(shù)、成員函數(shù)的調(diào)用,統(tǒng)一使用std::invoke(f,pArgsList)這種簡捷形式進(jìn)行調(diào)用[13]。當(dāng)然,成員函數(shù)調(diào)用時(shí),對象實(shí)例必須放在 pArgsList的首位,作為第一個(gè)參數(shù)。在本項(xiàng)目中,便使用了C++17的這一新技術(shù)簡化代碼設(shè)計(jì)。

5? ?C++17泛型函數(shù)容器的完整實(shí)現(xiàn)代碼(Complete implementation code of C++17 generic function container)

5.1? ?泛型函數(shù)容器的完整實(shí)現(xiàn)代碼

以下便是本文介紹的泛型函數(shù)容器的完整實(shí)現(xiàn)代碼。用戶需要注意的是,以下代碼是基于C++17新標(biāo)準(zhǔn)實(shí)現(xiàn)的,需要在支持C++17的編譯器中才能夠正常編譯,比如Visual Studio 2017 15.3[14]、CodeBlocks 17.12 with GCC 7.2及以上版本[15]。

6? 泛型函數(shù)容器的實(shí)際使用(Actual use of generic function container)

下面代碼測試了泛型函數(shù)容器的實(shí)際使用。測試可以分為注冊函數(shù)時(shí)輸入key和未輸入key兩大類。每一大類都可以實(shí)現(xiàn)無返回值普通函數(shù)、帶返回值普通函數(shù)、函數(shù)模板、成員函數(shù)、函數(shù)對象、lambda表達(dá)式、重載函數(shù)、某些特殊函數(shù)等的注冊和調(diào)用。

6.1? ?實(shí)際使用的測試代碼

6.2? ?重載函數(shù)和某些特殊函數(shù)的注冊調(diào)用測試

前面已經(jīng)提到,本項(xiàng)目解決了重載函數(shù)和某些特殊函數(shù)的注冊調(diào)用問題。具體測試代碼如下。

7? ?結(jié)論(Conclusion)

綜上所述,泛型函數(shù)容器可以將任意類型的函數(shù)用一個(gè)key進(jìn)行注冊以供其他程序調(diào)用,它的使用可以解耦對象之間的調(diào)用關(guān)系,有利于實(shí)現(xiàn)高內(nèi)聚、低耦合的軟件設(shè)計(jì)原則。C++標(biāo)準(zhǔn)庫中并沒有這樣的容器,用C++舊標(biāo)準(zhǔn)實(shí)現(xiàn)也很困難、很低效。C++1x等新標(biāo)準(zhǔn)發(fā)布后,出現(xiàn)了一些更好的實(shí)現(xiàn)方式。本文便是在已有設(shè)計(jì)的基礎(chǔ)之上,基于C++17新標(biāo)準(zhǔn),利用if constexpr、fold expression、std::invoke等新技術(shù),提供一種泛型函數(shù)容器的實(shí)現(xiàn)方式。測試結(jié)果表明,該實(shí)現(xiàn)方式簡潔高效地實(shí)現(xiàn)了任意類型函數(shù)的注冊和調(diào)用,并且解決了重載函數(shù)和某些特殊函數(shù)的注冊調(diào)用問題,可以顯著降低耦合性、提高代碼復(fù)用性。

參考文獻(xiàn)(References)

[1] Ofenbeck G,Rompf T,Püschel M.Staging for generic programming in space and time[C].The ACM SIGPLAN International Conference.ACM,2017:15-28.

[2] Bemardi ML,Cimitile M,Lucca GD.Design pattem detection using a DSL-driven graph matching approach[J].Journal of Software Evolution&Process,2014,26(12):1233-1266.

[3] B Rasool G,Mader P.A customizable approach to design pattems recognition based 011 feature types[J].Arabian Journal for Science&Engineering,2014,39(12):8851-8873.

[4] Chen Yewang,Jiang Zhixiong,Zhao Wenyun,et al.Generic component:a generic programming approach[EB/OL].https://www.computer.org/csdl/proceedings/cit/2007/2983/00/29830087-abs.html,2018 IEEE.

[5] Yallop J.Staging,generic programming[M].New York:ACM,2016:85-96.

[6] 符號修飾(name decoration)與函數(shù)簽名(function signature)[EB/OL].https://blog.csdn.net/weiwangchao_/article/details/7165467,2011-12-30.

[7] std::function[EB/OL].https://en.cppreference.com/w/cpp/utility/functional/function,2018-06-15.

[8] Bjarne Stroustrup.The C++ Programming Language Fourth Edition[M].USA:Addison-Wesley Professional,2013:967.

[9] std::placeholders[EB/OL].https://en.cppreference.com/w/cpp/utility/functional/placeholders,2018-06-15.

[10] Stanley B,Lippman.C++ Primer 5th Edition[M].USA:Addison-Wesley Professional,2012:572-574.

[11] if statement,attr(optional)if constexpr(optional)(init-statement(optional)condition)statement-true else statement-false[EB/OL].https://en.cppreference.com/w/cpp/language/if,2018-08-21.

[12] Fold expression(since C++17)[EB/OL].https://en.cppreference.com/w/cpp/language/fold,2018-07-19.

[13] std::invoke[EB/OL].https://en.cppreference.com/w/cpp/utility/functional/invoke,2018-07-06.

[14] C++ conformance improvements in Visual Studio 2017 versions[EB/OL].https://docs.microsoft.com/en-us/cpp/cpp-conformance-improvements-2017?view=vs-2017,2018-08-15.

[15] C++ Standards Support in GCC[EB/OL].https://gcc.gnu.org/projects/cxx-status.html,2018-09-30.

[16] 獲得函數(shù)返回值類型、參數(shù)tuple、成員函數(shù)指針中的對象類型[EB/OL].https://www.cnblogs.com/ybmj/p/9651227.html,2018-09-15.

猜你喜歡
型函數(shù)調(diào)用容器
Different Containers不同的容器
幾類“對勾”型函數(shù)最值問題的解法
難以置信的事情
核電項(xiàng)目物項(xiàng)調(diào)用管理的應(yīng)用研究
LabWindows/CVI下基于ActiveX技術(shù)的Excel調(diào)用
基于系統(tǒng)調(diào)用的惡意軟件檢測技術(shù)研究
Orlicz Sylvester Busemann型函數(shù)的極值研究
V-型函數(shù)的周期點(diǎn)
用共軛法解Dhombres型函數(shù)方程
利用RFC技術(shù)實(shí)現(xiàn)SAP系統(tǒng)接口通信
扬州市| 柘城县| 东乌| 巴彦淖尔市| 内黄县| 舟曲县| 黄陵县| 富锦市| 沅陵县| 正宁县| 洪泽县| 新余市| 洛阳市| 兰考县| 泸州市| 赫章县| 商洛市| 云林县| 洱源县| 若尔盖县| 大同县| 车险| 宜君县| 阿瓦提县| 山西省| 高阳县| 东宁县| 西畴县| 顺昌县| 崇礼县| 保山市| 金乡县| 深水埗区| 确山县| 民勤县| 汨罗市| 金川县| 黄石市| 浠水县| 长泰县| 陈巴尔虎旗|