0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何通過(guò)poly實(shí)現(xiàn)C++編譯期多態(tài)

C語(yǔ)言與CPP編程 ? 來(lái)源:C語(yǔ)言與CPP編程 ? 作者:C語(yǔ)言與CPP編程 ? 2022-12-05 09:10 ? 次閱讀

引言

前面的文章中我們更多的聚焦在運(yùn)行期反射,本篇我們將聚焦在一個(gè)與反射使用的機(jī)制有所類(lèi)同,但更依賴(lài)編譯期特性的機(jī)制-》編譯期多態(tài)實(shí)現(xiàn)。

c++最近幾版的更新添加了大量的compiler time特性支持,社區(qū)輪子的熱情又進(jìn)一步高漲。這幾年go與rust等語(yǔ)言也發(fā)展壯大,那么,我們能不能在c++中實(shí)現(xiàn)類(lèi)似go interface和rust traits的機(jī)制呢?

答案是肯定的,開(kāi)源社區(qū)早已經(jīng)開(kāi)始了自己的行動(dòng),dyno與folly::poly都已經(jīng)有了自己的實(shí)現(xiàn)。兩者的實(shí)現(xiàn)思路基本一致,差別主要在于dyno使用了boost::hana和其他一些第三方庫(kù)來(lái)完成整體機(jī)制的實(shí)現(xiàn)。

而folly::poly出來(lái)的晚一些,主要使用c++的新特性來(lái)實(shí)現(xiàn)相關(guān)的功能,依賴(lài)比較少,所以本文將更多的以poly的實(shí)現(xiàn)來(lái)分析編譯期多態(tài)的整體實(shí)現(xiàn)。

一、從c++的運(yùn)行時(shí)多態(tài)說(shuō)起

(一)一個(gè)簡(jiǎn)單的例子

struct Vehicle {
  virtual void accelerate() = 0;
  virtual ~Vechicle() {}
};


struct Car: public Vehicle {
  void accelerate() override;
};


struct Truck: public Vehicle {
  void accelerate() override;  
};

(二)對(duì)應(yīng)的運(yùn)行時(shí)內(nèi)存結(jié)構(gòu)

(三)運(yùn)行時(shí)多態(tài)帶來(lái)的問(wèn)題

性能問(wèn)題

大量的文章都提到,因?yàn)関irtual table的存在,對(duì)比純c的實(shí)現(xiàn),c++運(yùn)行時(shí)多態(tài)的使用會(huì)付出額外的性能開(kāi)銷(xiāo)。

指針帶來(lái)的問(wèn)題

運(yùn)行時(shí)多態(tài)一般多配合指針一起使用,這也導(dǎo)致基本相關(guān)代碼都是配合堆內(nèi)存來(lái)使用的,后續(xù)又引入了智能指針緩解堆內(nèi)存分配導(dǎo)致的額外心智負(fù)擔(dān),但智能指針的使用本身又帶來(lái)了其他問(wèn)題。

侵入性問(wèn)題

類(lèi)繼承需要強(qiáng)制指定子類(lèi)的基類(lèi),當(dāng)我們引入第三方庫(kù)的時(shí)候,要么不可避免的需要對(duì)其進(jìn)行修改,要么需要額外的包裝類(lèi),這些都會(huì)帶來(lái)復(fù)雜度的上升和性能的下降。還有一些其他的問(wèn)題,這里就不再展開(kāi)了,最近的cppconn多態(tài)本身相關(guān)的討論也是一個(gè)熱點(diǎn),許多項(xiàng)目開(kāi)始嘗試用自己的方法試圖解決運(yùn)行時(shí)多態(tài)的問(wèn)題,感興趣的可以自行去了解相關(guān)的內(nèi)容。

本部分例子和內(nèi)容主要來(lái)自Louis Dionne的Runtime Polymorphism: Back To The Basics。

二、dyno與poly的實(shí)現(xiàn)思路

(一)dyno與poly的目的-編譯期多態(tài)

dyno想達(dá)成的效果其實(shí)就是實(shí)現(xiàn)編譯期多態(tài),如作者所展示的代碼片段:

interface Vechicle { void accelerate(); };


namespace lib{
  struct Motorcycle { void accelerate(); };
}
struct Car { void accelerate(); };
struct Truck { void accelerate(); };


int main() {
  std::vector vehicles;
  vehicles.push_back(Car{...});
  vehicles.push_back(Truck{...});
  vehicles.push_back(lib::Motorcycle{...});


  for(auto& vehicle: vehicles) {
    vehicle.accelerate();
  }
}

想法很美好, 但現(xiàn)實(shí)是殘酷的, 并沒(méi)有interface存在, 在可預(yù)知的一段時(shí)間里, 也不會(huì)有, 那么如果要自己實(shí)現(xiàn)相關(guān)的機(jī)制, 該如何來(lái)達(dá)成呢? 我們?cè)谙挛闹邢葋?lái)看一下整體的實(shí)現(xiàn)思路。

(二)編譯期多態(tài)的設(shè)計(jì)思路

參考前面的運(yùn)行時(shí)多態(tài)模型:

dyno的思路比較直接,嘗試使用兩個(gè)獨(dú)立的部分來(lái)解決編譯期多態(tài)的支持問(wèn)題:

Storage policy-負(fù)責(zé)對(duì)象的存儲(chǔ)。

VTable policy-負(fù)責(zé)函數(shù)分發(fā)調(diào)用。

folly::Poly的實(shí)現(xiàn)思路大量參考了dyno,與dyno一致,也是同樣的結(jié)構(gòu)。我們繼續(xù)以Vechicle舉例,假設(shè)真的存在Vechicle對(duì)象,那么它的組織肯定是如下圖所示的:

通過(guò)這種結(jié)構(gòu),我們就能正常的訪問(wèn)到Car等具體對(duì)象的accelerate()方法了,原理上還是比較簡(jiǎn)潔的,但是要做到完全的編譯期多態(tài),并不是一個(gè)簡(jiǎn)單的事情。接下來(lái)我們先來(lái)看一個(gè)poly的示例代碼,先從應(yīng)用側(cè)了解一下它的使用。

三、poly的示例代碼

我們還是以Vechicle為例,給出一段poly的示例代碼:

#include 


struct IVehicle {
  // Define the interface for vehicle
  template <class Base> struct Interface : Base {
    void accelerate() const {
      folly::poly_call<0>(*this);
    }
  };
  // Define how concrete types can fulfill that interface (in C++17):
  template <class T> using Members = folly::PolyMembers<&T::accelerate>;
};


using vehicle = folly::Poly;




struct Car {
  void accelerate() const {
    std::cout << "Car accelerate!" << std::endl;
  }
};


struct Trunk {
  void accelerate() const {
    std::cout << "Trunk accelerate!" << std::endl;
  }
};




void accel_func(vehicle const& v) {
  v.accelerate();
}


int main() {
  accel_func(Car{});  // Car accelerate
  accel_func(Trunk{});  // Trunk accelerate
  return 0;
}

從上面的示例可以看到,poly的封裝使用還是比較簡(jiǎn)潔的,主要是兩個(gè)輔助對(duì)象的定義:

IVehicle 類(lèi)的定義

vehicle類(lèi)的定義

(一)IVehicle類(lèi)

struct IVehicle {
  // Define the interface for vehicle
  template <class Base> struct Interface : Base {
    void accelerate() const {
      folly::poly_call<0>(*this);
    }
  };
  // Define how concrete types can fulfill that interface (in C++17):
  template <class T> using Members = folly::PolyMembers<&T::accelerate>;
};

IVehicle類(lèi)主要提供兩個(gè)功能:

通過(guò)內(nèi)嵌類(lèi)型Members來(lái)完成接口包含的所有成員的定義,如上例中的&T::accelerate。

通過(guò)內(nèi)嵌類(lèi)型Interface提供類(lèi)型擦除后的poly《》對(duì)象的訪問(wèn)接口。

兩者的部分信息其實(shí)有所重復(fù),另外因?yàn)閜oly是基于c++17特性的,所以也沒(méi)有使用concept對(duì)Interface的類(lèi)型提供約束,這個(gè)地方約束性和簡(jiǎn)潔性上會(huì)有一點(diǎn)折扣。

(二) vehicle類(lèi)

using vehicle = folly::Poly《IVehicle》;

這個(gè)類(lèi)使用我們前面定義的IVehicle類(lèi)來(lái)定義一個(gè)folly::Poly《IVechicle》容器對(duì)象,所有滿(mǎn)足IVehicle定義的類(lèi)型都可以被它所容納,與std::any類(lèi)似,只是std::any用于裝填任意類(lèi)型,folly::Poly《》只能用來(lái)裝填符合相關(guān)Interface定義的對(duì)象,比如上面定義的vehicle,就能用來(lái)容納前面示例中定義的Car和Trunk等實(shí)現(xiàn)了void accelerate() const方法的類(lèi)型。同樣,區(qū)別于std::any只是用作一個(gè)萬(wàn)能容器,這里的vehicle本身也是支持函數(shù)調(diào)用的,比如例子中的:accelerate()。

(三)示例小結(jié)

通過(guò)上面的示例代碼,我們對(duì)poly的使用有了初步的了解,從上面的代碼中可以看出,編譯期多態(tài)的使用還是比較簡(jiǎn)潔的,整體過(guò)程跟c++標(biāo)準(zhǔn)的繼承比較類(lèi)似,有幾點(diǎn)差別比較大:

我們不需要侵入性的去指定子類(lèi)的基類(lèi),我們通過(guò)非侵入性的方式來(lái)使用poly庫(kù)。

我們是通過(guò)構(gòu)建的folly::Poly《》來(lái)完成對(duì)各種子類(lèi)型的容納的,而不是直接使用基類(lèi)來(lái)進(jìn)行類(lèi)型退化再統(tǒng)一存儲(chǔ)所有子類(lèi),這樣也就避免了繼承一般搭配堆內(nèi)存分配使用的問(wèn)題。

那么,整套機(jī)制是如何實(shí)現(xiàn)的呢? 我們?cè)谙挛闹袑⒕唧w展開(kāi)。

四、關(guān)于實(shí)現(xiàn)的猜想

前面的文章中我們介紹了運(yùn)行時(shí)反射的相關(guān)機(jī)制,所以類(lèi)似poly這種使用側(cè)的包裝,如果我們拋開(kāi)性能,考慮用反射實(shí)現(xiàn)類(lèi)似機(jī)制,還是比較容易的。

(一)VTable與meta::class

VTable的概念其實(shí)與前面的篇章里提到的meta::class功能基本一致:

meta::class上存的meta::method都是已經(jīng)完成類(lèi)型擦除的版本,所以我們可以通過(guò)名稱(chēng)很容易的從中查詢(xún)出自己需要的函數(shù),比如上例中的accelerate,相關(guān)代碼類(lèi)似于:

const reflection::Function* accel_func = nullptr;
car_meta_class.TryGetFunction("accelerate", accel_func);
runtime::Call(*accel_func, car_obj);

當(dāng)然,此處我們省略了meta::class的注冊(cè)過(guò)程,也省略了car_obj這個(gè)UserObject的創(chuàng)建過(guò)程。

(二)folly::Poly《》與UserObject

我們很容易想到,使用UserObject作為Car和Trunk的容器,能夠起到跟folly:Poly《》類(lèi)似的效果。利用UserObject,我們可以很好的完成各種不同類(lèi)型對(duì)象的類(lèi)型擦除,很好的完全不同類(lèi)型對(duì)象的統(tǒng)一存儲(chǔ)和函數(shù)參數(shù)傳遞的目的。

(三)運(yùn)行時(shí)反射實(shí)現(xiàn)的例子

這樣,對(duì)于原來(lái)的例子,省略meta class的注冊(cè)過(guò)程,大致的代碼如下:

struct Car {
  void accelerate() const {
    std::cout << "Car accelerate!" << std::endl;
  }
};


struct Trunk {
  void accelerate() const {
    std::cout << "Trunk accelerate!" << std::endl;
  }
};




void accel_func(UserObject& v) {
  auto& meta_class = v.GetClass();
  const reflection::Function* accel_func = nullptr;
  meta_class.TryGetFunction("accelerate", accel_func);
  runtime::Call(*accel_func, v);
}


int main() {
  //Car meta class register ignore here
  // ...


  //Trunk meta class register ignore here


  accel_func(UserObject::MakeOwned(Car{}));  // Car accelerate
  accel_func(UserObject::MakeOwned(Trunk{}));  // Trunk accelerate


  return 0;
}

功能上似乎是那么回事,甚至因?yàn)檫\(yùn)行時(shí)反射本身各部分類(lèi)型擦除很徹底,好像實(shí)現(xiàn)上更靈活了,但是,這其實(shí)只是形勢(shì)上實(shí)現(xiàn)了一個(gè)運(yùn)行時(shí)interface like的功能,我們?nèi)菀卓闯?,這個(gè)實(shí)現(xiàn)達(dá)成了以下目的:

非侵入性,Car與Trunk不需要額外的修改就能支持interface like的功能。

我們可以利用類(lèi)型擦除的UserObject對(duì)Car和Trunk這些不同類(lèi)型的對(duì)象進(jìn)行存儲(chǔ)。

不同對(duì)象上的accelerate()實(shí)現(xiàn)可以被正確的調(diào)用。

同時(shí),這個(gè)實(shí)現(xiàn)存在諸多的問(wèn)題:

運(yùn)行時(shí)實(shí)現(xiàn),性能肯定有比較大的折扣。

比較徹底的類(lèi)型擦除帶來(lái)的問(wèn)題,整個(gè)實(shí)現(xiàn)一點(diǎn)都不compiler time,編譯期的基礎(chǔ)類(lèi)型檢查也完全沒(méi)有了。

那么我們肯定會(huì)想到,poly是如何利用compiler time特性,實(shí)現(xiàn)更快的interface like的版本的呢? 這也是我們下一章節(jié)開(kāi)始想展開(kāi)的內(nèi)容。

五、poly的實(shí)現(xiàn)分析

在開(kāi)始分析前,我們先來(lái)回顧一下前面的示例代碼:

using vehicle = folly::Poly;
void accel_func(vehicle const& v) {
  v.accelerate();
}


int main() {
  accel_func(Car{});  // Car accelerate
  accel_func(Trunk{});
  return 0;
}

一切的起點(diǎn)發(fā)生在accel_func()將臨時(shí)構(gòu)造的Car{}和Trunk{}向vehicle轉(zhuǎn)換的過(guò)程中,而我們知道vehicle實(shí)際類(lèi)型是folly::Poly,這也是一個(gè)Duck Type,可以容納所有滿(mǎn)足IVehicle聲明的類(lèi)型,如上例中的Car和Trunk。

上例中,Car和Trunk類(lèi)型向Duck Type類(lèi)型轉(zhuǎn)換的代碼如下:

template <class I>
template ::value, int>>
inline PolyVal::PolyVal(T&& t) {
  using U = std::decay_t;
  //some compiler time && runtime check ignore here
  //...
  if (inSitu()) {
    auto const buff = static_cast<void*>(&_data_()->buff_);
    ::new (buff) U(static_cast(t));
  } else {
    _data_()->pobj_ = new U(static_cast(t));
  }
  vptr_ = vtableFor();
}

非常直接的代碼,可以看出與dyno的思路完全一致,主要完成我們前面提到過(guò)的兩件事:

Storage policy-分配合適的空間以存儲(chǔ)對(duì)象。

VTable policy-為對(duì)象關(guān)聯(lián)正確的VTable。

當(dāng)然,實(shí)際的實(shí)現(xiàn)過(guò)程其實(shí)還有比較多的細(xì)節(jié),我們先來(lái)具體看一下storage與VTable這兩部分的實(shí)現(xiàn)細(xì)節(jié)。

(一)storage處理

整個(gè)poly的storage處理完全參考了dyno的實(shí)現(xiàn),當(dāng)然并沒(méi)有像dyno那樣提供多種storage policy,而是固定的分配策略:

  if (inSitu()) {
    auto const buff = static_cast<void*>(&_data_()->buff_);
    ::new (buff) U(static_cast(t));
  } else {
    _data_()->pobj_ = new U(static_cast(t));
  }

適合原地構(gòu)造的,則直接使用replacement new來(lái)原地構(gòu)造對(duì)象(性能最優(yōu)的方式),否則則還是使用堆分配。這里會(huì)用到一個(gè)Data類(lèi)型,也是完全copy的dyno的實(shí)現(xiàn),定義如下:

struct Data {
  Data() = default;
  // Suppress compiler-generated copy ops to not copy anything:
  Data(Data const&) {}
  Data& operator=(Data const&) { return *this; }
  union {
    void* pobj_ = nullptr;
    std::aligned_storage_t<sizeof(double[2])> buff_;
  };
};

其實(shí)我們已經(jīng)不難猜到inSitu()的實(shí)現(xiàn)了,其中肯定有對(duì)對(duì)象大小的判斷:

template <class T>
inline constexpr bool inSitu() noexcept {
  return !std::is_reference::value &&
      sizeof(std::decay_t) <= sizeof(Data) &&
      std::is_nothrow_move_constructible<std::decay_t>::value;
}

除了原地構(gòu)造的大小限制外-寫(xiě)死的兩個(gè)double大小,poly增加了對(duì)無(wú)異常移動(dòng)構(gòu)造的約束,也就是對(duì)象的移動(dòng)構(gòu)造如果不是nothrow的,就算大小滿(mǎn)足要求,也依然會(huì)使用堆分配進(jìn)行構(gòu)造。

storage這部分主要還是使用SBO的優(yōu)化策略,這部分dyno相關(guān)的視頻中有詳細(xì)的介紹,poly的實(shí)現(xiàn)完全照搬了那部分思路,感興趣的同學(xué)可以自行去看一下參考部分的相關(guān)視頻,了解更多的細(xì)節(jié),也包括dyno作者自己做的性能分析。

(二)VTable處理

vptr_ = vtableFor《I, U》();

處理的難點(diǎn)

對(duì)于Car和Trunk,它們同名的void accelerate()函數(shù),其實(shí)類(lèi)型并不相同,這是因?yàn)轭?lèi)的成員函數(shù)都隱含了一個(gè)this指針,將自己的類(lèi)型帶入進(jìn)去了。簡(jiǎn)單的保存成員函數(shù)的指針的方式肯定不適用了,另外因?yàn)槲覀冃枰罱K得到統(tǒng)一的Duck Type-vehicle,我們也需要統(tǒng)一Car和Trunk的VTable類(lèi)型,所以這里肯定是要對(duì)接口函數(shù)的類(lèi)型做一次擦除操作的。

另外,因?yàn)槲覀冃枰M可能的避免運(yùn)行時(shí)開(kāi)銷(xiāo),所以在我們使用Duck Type對(duì)對(duì)象的相關(guān)接口,如上面的accelerate()進(jìn)行訪問(wèn)的時(shí)候,我們希望中間過(guò)程是足夠高效的。

poly是如何做到這兩點(diǎn)的呢? 我們帶著這兩個(gè)疑問(wèn),逐步深入相關(guān)的代碼了解具體的實(shí)現(xiàn)。

  • vtableFor<>實(shí)現(xiàn)

template <class I, class T>
constexpr VTable const* vtableFor() noexcept {
  return &StaticConst>>::value;
}

這個(gè)地方的StaticConst是一個(gè)類(lèi)似singleton的封裝:

//  StaticConst
//
//  A template for defining ODR-usable constexpr instances. Safe from ODR
//  violations and initialization-order problems.


template <typename T>
struct StaticConst {
  static constexpr T value{};
};


template <typename T>
constexpr T StaticConst::value;

這樣我們就有了一個(gè)根據(jù)類(lèi)型來(lái)查詢(xún)?nèi)治ㄒ籚Table指針的機(jī)制了,足夠高效。

核心問(wèn)題的解決都是發(fā)生在VTableFor這個(gè)類(lèi)型上的,我們下面來(lái)具體看一下它的實(shí)現(xiàn)。

  • VTableFor與VTable的實(shí)現(xiàn)

template <class I, class T>
struct VTableFor : VTable {
  constexpr VTableFor() noexcept : VTable{Type{}} {}
};


template <
    class I,
    class = MembersOf<I, Archetype>>,
    class = SubsumptionsOf<I>>
struct VTable;


template <class I, FOLLY_AUTO... Arch, class... S>
struct VTable, TypeList>
    : BasePtr..., std::tuple...> {
 private:
  template <class T, FOLLY_AUTO... User>
  constexpr VTable(Type, PolyMembers) noexcept
      : BasePtr{vtableFor()}...,
        std::tuple...>{thunk_()...},
        state_{inSitu() ? State::eInSitu : State::eOnHeap},
        ops_{getOps()} {}


 public:
  constexpr VTable() noexcept
      : BasePtr{vtable()}...,
        std::tuple...>{
            static_cast>(throw_())...},
        state_{State::eEmpty},
        ops_{&noopExec} {}


  template <class T>
  explicit constexpr VTable(Type) noexcept
      : VTable{Type{}, MembersOf{}} {}


  State state_;
  void* (*ops_)(Op, Data*, void*);
};

這個(gè)地方的代碼實(shí)現(xiàn)其實(shí)有點(diǎn)繞,一開(kāi)始我以為是使用的CTAD,c++17的模板參數(shù)自動(dòng)推導(dǎo)的功能,按照類(lèi)似的方式在自己的代碼上嘗試始終失敗,最后才發(fā)現(xiàn)跟CTAD一點(diǎn)關(guān)系沒(méi)有。

首先是第一點(diǎn),VTable通過(guò)I(也就是例子中的IVehicle),就能夠完全構(gòu)建出自己的類(lèi)型了,這也是為什么Car與Trunk的VTable類(lèi)型完全一致的原因,因?yàn)轭?lèi)型定義上,完全不依賴(lài)具體的Car和Trunk。

然后是第二點(diǎn),VTable的第一個(gè)構(gòu)造函數(shù)為VTable提供實(shí)際的數(shù)據(jù)來(lái)源,這里才會(huì)用到具體的類(lèi)型Car和Trunk。

那么VTable的設(shè)計(jì)是如何實(shí)現(xiàn)具體的類(lèi)型分離的呢? 這里直接給出答案,我們可以認(rèn)為,poly對(duì)接口函數(shù)做了一個(gè)部分的類(lèi)型擦除,相比于之前介紹的反射對(duì)所有函數(shù)進(jìn)行類(lèi)型統(tǒng)一,poly的函數(shù)擦除方法可以說(shuō)是剛剛好,以上文中的accelerate()舉例,在Car中的時(shí)候原始類(lèi)型為:


	
void(const Car::*)();

最終類(lèi)型擦除后產(chǎn)生的函數(shù)類(lèi)型為:

void(*)(const folly::Data &);

這樣,不管是Car和Trunk,它們對(duì)應(yīng)接口的類(lèi)型就被統(tǒng)一了,同時(shí),Data本身也跟我們前面提到的Duck Type-PolyVal關(guān)聯(lián)起來(lái)了。

這種轉(zhuǎn)換老司機(jī)們肯定容易想到lambda,lambda肯定也是用于處理這種參數(shù)統(tǒng)一的利器,不過(guò)poly這里選用了一種編譯開(kāi)銷(xiāo)更有優(yōu)勢(shì)的方式:

template <
    class T,
    FOLLY_AUTO User,
    class I,
    class = ArgTypes,
    class = Bool>
struct ThunkFn {
  template <class R, class D, class... As>
  constexpr /* implicit */ operator FnPtr() const noexcept {
    return nullptr;
  }
};


template <class T, FOLLY_AUTO User, class I, class... Args>
struct ThunkFn<
    T,
    User,
    I,
    TypeList,
    Bool<
        !std::is_const>::value ||
        IsConstMember>::value>> {
  template <class R, class D, class... As>
  constexpr /* implicit */ operator FnPtr() const noexcept {
    struct _ {
      static R call(D& d, As... as) {
        return folly::invoke(
            memberValue(),
            get(d),
            convert(static_cast(as))...);
      }
    };
    return &_::call;
  }
};

通過(guò)一個(gè)結(jié)構(gòu)體的靜態(tài)函數(shù)來(lái)繞開(kāi)lambda來(lái)對(duì)函數(shù)的參數(shù)類(lèi)型進(jìn)行轉(zhuǎn)換,當(dāng)然,通過(guò)這里我們也能了解到具體的接口函數(shù)的執(zhí)行過(guò)程了,有幾點(diǎn)需要注意一下:

  • folly::invoke()的功能與標(biāo)準(zhǔn)庫(kù)的std::invoke()功能一致。

  • get(d)完成Data類(lèi)型到具體類(lèi)型的還原。

  • 與反射中類(lèi)似,也存在對(duì)參數(shù)表中的參數(shù)的convert的處理,這塊就不再展開(kāi)了,基本都是原始類(lèi)型參數(shù)的派發(fā),因?yàn)橐恍┻M(jìn)階功能存在Poly類(lèi)型轉(zhuǎn)換派發(fā)的情況,此處不再詳細(xì)描述了。

再回到多個(gè)接口函數(shù)的存儲(chǔ)上,這個(gè)是通過(guò)繼承的std::tuple<>來(lái)完成的,所以我們?cè)贗nterface的定義中也會(huì)發(fā)現(xiàn)這樣的模板特化用法,實(shí)際就是取這個(gè)tuple<>中對(duì)應(yīng)位置的元素。

struct VTable, TypeList>
    : BasePtr..., std::tuple...> 


template <class T, FOLLY_AUTO... User>
  constexpr VTable(Type, PolyMembers) noexcept
      : BasePtr{vtableFor()}...,
        std::tuple...>{thunk_()...},
        state_{inSitu() ? State::eInSitu : State::eOnHeap},
        ops_{getOps()} {}

trunk_()函數(shù)完成對(duì)上面函數(shù)類(lèi)型轉(zhuǎn)換函數(shù)ThunkFn()的調(diào)用,這樣整個(gè)虛表中最重要的信息就構(gòu)造完成了。

(三)關(guān)于性能

我們直接以windows上的release版為例,通過(guò)生成的asm大致推測(cè)poly實(shí)際的運(yùn)行時(shí)性能:

//...
accel_func(Car{});  // Car accelerate
00007FF696421166  mov         qword ptr [rsp+20h],0  
00007FF69642116F  lea         rdi,[__ImageBase (07FF696420000h)]  
00007FF696421176  lea         rax,[rdi+33B0h]  
00007FF69642117D  mov         qword ptr [rsp+30h],rax  
00007FF696421182  lea         rcx,[rsp+20h]  
00007FF696421187  call         ?? ::`folly::ThunkFn,std::integral_constant1> >::operatorconst > void (__cdecl*)(folly::Data const &)'::`2'::call (07FF696421490h)  
00007FF69642118C  nop  
00007FF69642118D  mov         rax,qword ptr [rsp+30h]  
00007FF696421192  mov         r9,qword ptr [rax+10h]  
00007FF696421196  xor         ecx,ecx  
00007FF696421198  xor         r8d,r8d  
00007FF69642119B  lea         rdx,[rsp+20h]  
00007FF6964211A0  call        r9  
00007FF6964211A3  nop  
  accel_func(Trunk{});  // Trunk accelerate
//...

到真正調(diào)用到實(shí)際的accelerate()函數(shù),編譯期的各種中間過(guò)程,基本都能被優(yōu)化掉,整體性能估計(jì)跟virtual dispatch接近或者更高,有時(shí)間再結(jié)合實(shí)際的工程示例測(cè)試一下相關(guān)的數(shù)據(jù),本篇性能相關(guān)的分析就先到這里了。

(四)poly小結(jié)

poly核心機(jī)制的實(shí)現(xiàn)并不復(fù)雜,主要也就是本章介紹的這部分,但poly還實(shí)現(xiàn)了一些進(jìn)階功能,比如interface之間的繼承,非成員函數(shù)的支持等,導(dǎo)致整個(gè)實(shí)現(xiàn)的復(fù)雜度飆升,感興趣的可以自行翻閱相關(guān)的代碼,推薦的熟悉順序是:

TypeList.h-里面封裝了大量類(lèi)型和類(lèi)型運(yùn)算相關(guān)的功能,整體思路類(lèi)似boost::mpl的meta function,但基本沒(méi)有其他依賴(lài),實(shí)現(xiàn)也足夠簡(jiǎn)單,值得一看。

PolyNode等其它用于支撐Interface繼承的結(jié)構(gòu)。

正常來(lái)說(shuō),熟悉了TypeList中的meta function以及常用的TypeFold等實(shí)現(xiàn),讀懂相關(guān)代碼不會(huì)存在太多的障礙。

另外,Windows上不推薦直接使用源碼編譯folly,依賴(lài)庫(kù)比較多,并且應(yīng)該很久沒(méi)人維護(hù)了,獲取dependency的python腳本都直接報(bào)錯(cuò),建議windows上直接使用vcpkg 安裝folly進(jìn)行使用,因?yàn)閒olly與boost 類(lèi)似,基本只有頭文件實(shí)現(xiàn),通過(guò)這種方式并不影響源碼的閱讀和調(diào)試。

六、總結(jié)

本篇我們重點(diǎn)介紹了編譯期多態(tài),也講到了它與反射的一些關(guān)聯(lián)和差異,最后結(jié)合poly的相關(guān)實(shí)現(xiàn)介紹了一些核心的技術(shù)點(diǎn)。當(dāng)然,就編譯期反射來(lái)說(shuō),我們還有更多可以做的內(nèi)容:

比如參考視頻中提到的結(jié)合未來(lái)的語(yǔ)言新特性如reflect,meta class來(lái)進(jìn)一步簡(jiǎn)化使用接口。

或者通過(guò)離線的方式做一部分代碼生成來(lái)進(jìn)一步簡(jiǎn)化使用側(cè)的Interface定義,甚至提供更強(qiáng)的編譯期約束等。

這些我們會(huì)嘗試在實(shí)際的落地中逐步完善,有相關(guān)的進(jìn)展再來(lái)分享了。

審核編輯:郭婷


聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C++
    C++
    +關(guān)注

    關(guān)注

    21

    文章

    2100

    瀏覽量

    73453
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4722

    瀏覽量

    68229

原文標(biāo)題:如何優(yōu)雅地實(shí)現(xiàn)C++編譯期多態(tài)?

文章出處:【微信號(hào):C語(yǔ)言與CPP編程,微信公眾號(hào):C語(yǔ)言與CPP編程】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    MSP430優(yōu)化C/C++編譯器v21.6.0.LTS

    電子發(fā)燒友網(wǎng)站提供《MSP430優(yōu)化C/C++編譯器v21.6.0.LTS.pdf》資料免費(fèi)下載
    發(fā)表于 11-08 14:57 ?0次下載
    MSP430優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b><b class='flag-5'>編譯</b>器v21.6.0.LTS

    ARM優(yōu)化C/C++編譯器 v20.2.0.LTS

    電子發(fā)燒友網(wǎng)站提供《ARM優(yōu)化C/C++編譯器 v20.2.0.LTS.pdf》資料免費(fèi)下載
    發(fā)表于 11-07 10:46 ?0次下載
    ARM優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b><b class='flag-5'>編譯</b>器 v20.2.0.LTS

    TMS320C6000優(yōu)化C/C++編譯器v8.3.x

    電子發(fā)燒友網(wǎng)站提供《TMS320C6000優(yōu)化C/C++編譯器v8.3.x.pdf》資料免費(fèi)下載
    發(fā)表于 11-01 09:35 ?0次下載
    TMS320<b class='flag-5'>C</b>6000優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b><b class='flag-5'>編譯</b>器v8.3.x

    TMS320C28x優(yōu)化C/C++編譯器v22.6.0.LTS

    電子發(fā)燒友網(wǎng)站提供《TMS320C28x優(yōu)化C/C++編譯器v22.6.0.LTS.pdf》資料免費(fèi)下載
    發(fā)表于 10-31 10:10 ?0次下載
    TMS320<b class='flag-5'>C</b>28x優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b><b class='flag-5'>編譯</b>器v22.6.0.LTS

    C7000優(yōu)化C/C++編譯

    電子發(fā)燒友網(wǎng)站提供《C7000優(yōu)化C/C++編譯器.pdf》資料免費(fèi)下載
    發(fā)表于 10-30 09:45 ?0次下載
    <b class='flag-5'>C</b>7000優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b><b class='flag-5'>編譯</b>器

    OpenHarmony標(biāo)準(zhǔn)系統(tǒng)C++公共基礎(chǔ)類(lèi)庫(kù)案例:HelloWorld

    1、程序簡(jiǎn)介該程序是基于凌蒙派OpenHarmony-v3.2.1標(biāo)準(zhǔn)系統(tǒng)C++公共基礎(chǔ)類(lèi)庫(kù)的簡(jiǎn)單案例:HelloWorld。主要講解C++公共基礎(chǔ)類(lèi)庫(kù)案例如何搭建和編譯。2、程序解析2.1、創(chuàng)建
    的頭像 發(fā)表于 08-13 08:23 ?422次閱讀
    OpenHarmony標(biāo)準(zhǔn)系統(tǒng)<b class='flag-5'>C++</b>公共基礎(chǔ)類(lèi)庫(kù)案例:HelloWorld

    c++編譯后鏈接失敗的原因?如何解決?

    /c++項(xiàng)目,將剛才新建的項(xiàng)目轉(zhuǎn)換為c++項(xiàng)目。 完成后點(diǎn)擊編譯,此時(shí)也是正常的。 新建一個(gè)cpp文件,將原項(xiàng)目的main.c中內(nèi)容全部拷貝到新建的cpp文件中保存,然后刪除原main
    發(fā)表于 07-25 08:13

    C++實(shí)現(xiàn)類(lèi)似instanceof的方法

    C++多態(tài)與繼承,但是很多人開(kāi)始學(xué)習(xí)C++,有時(shí)候會(huì)面臨一個(gè)常見(jiàn)問(wèn)題,就是如何向下轉(zhuǎn)型,特別是不知道具體類(lèi)型的時(shí)候,這個(gè)時(shí)候就希望C++ 可以向Java或者Python中有insta
    的頭像 發(fā)表于 07-18 10:16 ?499次閱讀
    <b class='flag-5'>C++</b>中<b class='flag-5'>實(shí)現(xiàn)</b>類(lèi)似instanceof的方法

    SEGGER編譯器優(yōu)化和安全技術(shù)介紹 支持最新CC++語(yǔ)言

    SEGGER編譯器是專(zhuān)門(mén)為ARM和RISC-V微控制器設(shè)計(jì)的優(yōu)化C/C++編譯器。它建立在強(qiáng)大的Clang前端上,支持最新的C
    的頭像 發(fā)表于 06-04 15:31 ?1362次閱讀
    SEGGER<b class='flag-5'>編譯</b>器優(yōu)化和安全技術(shù)介紹 支持最新<b class='flag-5'>C</b>和<b class='flag-5'>C++</b>語(yǔ)言

    keil用c++編譯含有rtos模塊時(shí)的錯(cuò)誤問(wèn)題怎么解決?

    和 rtos,設(shè)置使用cpp編譯,c99通過(guò)的程序編譯錯(cuò)誤一大堆,主要在usb和cmsis_os里,這里隨便粘貼其中一條錯(cuò)誤C:/Users
    發(fā)表于 05-09 08:29

    RX系列V3.06.00的C/C++編譯器包數(shù)據(jù)手冊(cè)

    電子發(fā)燒友網(wǎng)站提供《RX系列V3.06.00的C/C++編譯器包數(shù)據(jù)手冊(cè).pdf》資料免費(fèi)下載
    發(fā)表于 01-26 15:57 ?1次下載
    RX系列V3.06.00的<b class='flag-5'>C</b>/<b class='flag-5'>C++</b><b class='flag-5'>編譯</b>器包數(shù)據(jù)手冊(cè)

    C++簡(jiǎn)史:C++是如何開(kāi)始的

    MISRA C++:2023,MISRA? C++ 標(biāo)準(zhǔn)的下一個(gè)版本,來(lái)了!為了幫助您做好準(zhǔn)備,我們介紹了 Perforce 首席技術(shù)支持工程師 Frank van den Beuken 博士撰寫(xiě)
    的頭像 發(fā)表于 01-11 09:00 ?513次閱讀
    <b class='flag-5'>C++</b>簡(jiǎn)史:<b class='flag-5'>C++</b>是如何開(kāi)始的

    C語(yǔ)言和C++中那些不同的地方

    ++11標(biāo)準(zhǔn)。根據(jù)不同的標(biāo)準(zhǔn),它們的功能也會(huì)有所不同,但是越新的版本支持的編譯器越少,所以本文在討論的時(shí)候使用的C語(yǔ)言標(biāo)準(zhǔn)是C89,C++標(biāo)準(zhǔn)是C
    的頭像 發(fā)表于 12-07 14:29 ?889次閱讀
    <b class='flag-5'>C</b>語(yǔ)言和<b class='flag-5'>C++</b>中那些不同的地方

    c++怎么開(kāi)始編程

    應(yīng)用程序、嵌入式系統(tǒng)和網(wǎng)絡(luò)應(yīng)用程序等各種領(lǐng)域。 在開(kāi)始編程之前,你需要安裝C++的編程環(huán)境。首先,你需要下載并安裝一個(gè)編譯器,比如微軟的Visual Studio、GNU的GCC或者Clang。這些編譯器可以將你的
    的頭像 發(fā)表于 11-27 15:56 ?875次閱讀

    c++多行注釋快捷鍵

    */ 結(jié)束。在這兩個(gè)標(biāo)記之間的所有內(nèi)容都會(huì)被視為注釋?zhuān)⑶也粫?huì)參與編譯和執(zhí)行。 為了添加或刪除多行注釋?zhuān)憧梢允褂?b class='flag-5'>C++的集成開(kāi)發(fā)環(huán)境(IDE)提供的快捷鍵。下面是一些常見(jiàn)的C++開(kāi)發(fā)環(huán)境中常用的多行注釋快捷鍵: Visual
    的頭像 發(fā)表于 11-22 10:24 ?7931次閱讀