19 橋接靜態(tài)多態(tài)與動(dòng)態(tài)多態(tài)

函數(shù)對(duì)象陨囊、指針std::function

  • 函數(shù)對(duì)象用于給模板提供一些可定制行為
#include <iostream>
#include <vector>

template<typename F>
void forUpTo(int n, F f)
{
  for (int i = 0; i < n; ++i) f(i);
}

void print(int i)
{
  std::cout << i << ' ';
}

int main()
{
  std::vector<int> v;
  forUpTo(5, [&v] (int i) { v.push_back(i); });
  forUpTo(5, print); // prints 0 1 2 3 4
}
  • forUpTo()可用于任何函數(shù)對(duì)象计贰,每次使用forUpTo()都將產(chǎn)生一個(gè)不同的實(shí)例化,這個(gè)模板很小授瘦,但如果很大則實(shí)例化的代碼也會(huì)很大闯第。一個(gè)限制代碼增長(zhǎng)的方法是將其改為無需實(shí)例化的非模板
void forUpTo(int n, void (*f)(int))
{
  for (int i = 0; i < n; ++i) f(i);
}
  • 但這樣就不允許接受lambda驼侠,因?yàn)閘ambda不能轉(zhuǎn)為函數(shù)指針
forUpTo(5, [&v] (int i) { v.push_back(i); }); // 錯(cuò)誤:lambda不能轉(zhuǎn)為void(*)(int)
#include <functional>

void forUpTo(int n, std::function<void(int)> f)
{
  for (int i = 0; i < n; ++i) f(i);
}
  • 這個(gè)實(shí)現(xiàn)提供了一些靜態(tài)多態(tài)方面的特點(diǎn)权埠,它能處理函數(shù)對(duì)象,但本身只是單個(gè)實(shí)現(xiàn)的非模板函數(shù)袍暴。它使用類型擦除(type erasure)的技術(shù)做到這點(diǎn)些侍,即在編譯期去掉不需要關(guān)心的原有類型(與實(shí)參推斷相反),類型擦除橋接了靜態(tài)多態(tài)與動(dòng)態(tài)多態(tài)的溝壑

實(shí)現(xiàn)std::function

  • std::function是一個(gè)廣義的函數(shù)指針形式政模,不同于函數(shù)指針的是娩梨,它能存儲(chǔ)一個(gè)lambda或其他任何帶有合適的operator()的函數(shù)對(duì)象。下面實(shí)現(xiàn)一個(gè)能替代std::function的類模板
#include <iostream>
#include <vector>
#include <type_traits>

template<typename R, typename... Args>
class B { // 橋接口:負(fù)責(zé)函數(shù)對(duì)象的所有權(quán)和操作
 public: // 實(shí)現(xiàn)為抽象基類览徒,作為類模板A動(dòng)態(tài)多態(tài)的基礎(chǔ)
  virtual ~B() {}  
  virtual B* clone() const = 0;
  virtual R invoke(Args... args) const = 0;
};

template<typename F, typename R, typename... Args>
class X : public B<R, Args...> { // 抽象基類的實(shí)現(xiàn)
 private:
  F f; // 參數(shù)化存儲(chǔ)的函數(shù)對(duì)象類型狈定,以實(shí)現(xiàn)類型擦除
 public:
  template<typename T>
  X(T&& f) : f(std::forward<T>(f)) {}

  virtual X* clone() const override
  {
    return new X(f);
  }

  virtual R invoke(Args... args) const override
  {
    return f(std::forward<Args>(args)...);
  }
};

// 原始模板
template<typename Signature>
class A;

// 偏特化
template<typename R, typename... Args>
class A<R(Args...)> {
 private:
  B<R, Args...>* bridge; // 該指針負(fù)責(zé)管理函數(shù)對(duì)象
 public:
  A() : bridge(nullptr) {}

  A(const A& other) : bridge(nullptr)
  {
    if (other.bridge)
    {
      bridge = other.bridge->clone();
    }
  }

  A(A& other) : A(static_cast<const A&>(other)) {}

  A(A&& other) noexcept : bridge(other.bridge)
  {
    other.bridge = nullptr;
  }

  template<typename F>
  A(F&& f) : bridge(nullptr) // 從任意函數(shù)對(duì)象構(gòu)造
  {
    using Functor = std::decay_t<F>;
    using Bridge = X<Functor, R, Args...>; // X的實(shí)例化存儲(chǔ)一個(gè)函數(shù)對(duì)象副本
    bridge = new Bridge(std::forward<F>(f)); // 派生類到基類的轉(zhuǎn)換,F(xiàn)丟失习蓬,類型擦除
  }

  A& operator=(const A& other)
  {
    A tmp(other);
    swap(*this, tmp);
    return *this;
  }

  A& operator=(A&& other) noexcept
  {
    delete bridge;
    bridge = other.bridge;
    other.bridge = nullptr;
    return *this;
  }

  template<typename F>
  A& operator=(F&& f)
  {
    A tmp(std::forward<F>(f));
    swap(*this, tmp);
    return *this;
  }

  ~A() { delete bridge; }

  friend void swap(A& fp1, A& fp2) noexcept
  {
    std::swap(fp1.bridge, fp2.bridge);
  }

  explicit operator bool() const
  {
    return bridge == nullptr;
  }

  R operator()(Args... args) const
  {
    return bridge->invoke(std::forward<Args>(args)...);
  }
};

void forUpTo(int n, A<void(int)> f)
{
  for (int i = 0; i < n; ++i) f(i);
}

void print(int i)
{
  std::cout << i << ' ';
}

int main()
{
  std::vector<int> v;
  forUpTo(5, [&v] (int i) { v.push_back(i); });
  forUpTo(5, print);
}
  • 上述A模板還不支持函數(shù)指針提供的一個(gè)操作:測(cè)試是否兩個(gè)A對(duì)象將調(diào)用相同的函數(shù)纽什。這需要橋接口B提供一個(gè)equals操作
virtual bool equals(const B* fb) const = 0;
  • 接著在X中實(shí)現(xiàn)
virtual bool equals(const B<R, Args...>* fb) const override
{
  if (auto specFb = dynamic_cast<const X*> (fb))
  {
    return f == specFb->f; // 要求f有operator==
  }
  return false;
}
  • 最終為A實(shí)現(xiàn)operator==
friend bool operator==(const A& f1, const A& f2) {
  if (!f1 || !f2)
  {
    return !f1 && !f2;
  }
  return f1.bridge->equals(f2.bridge); // equals要求operator==
}

friend bool operator!=(const A& f1, const A& f2)
{
  return !(f1 == f2);
}
  • 這個(gè)實(shí)現(xiàn)還有一個(gè)問題:如果A用沒有operator==的函數(shù)對(duì)象賦值或初始化,編譯將報(bào)錯(cuò)躲叼,即使A的operator==還沒被使用芦缰。這個(gè)問題來源于類型擦除:一旦A被賦值或初始化,就丟失了函數(shù)對(duì)象的類型(派生類到基類的轉(zhuǎn)換)枫慷,這就要求在實(shí)例化前得知類型信息让蕾,這個(gè)信息包括對(duì)一個(gè)函數(shù)對(duì)象的operator==的調(diào)用浪规。為此可以用SFINAE-based traits檢查operator==是否可用
template<typename T>
class IsEqualityComparable {
 private:
  static void* conv(bool);
  template<typename U>
  static std::true_type test(
    decltype(conv(std::declval<const U&>() == std::declval<const U&>())),
    decltype(conv(!(std::declval<const U&>() == std::declval<const U&>())))
  );

  template<typename U>
  static std::false_type test(...);
 public:
  static constexpr bool value = decltype(test<T>(nullptr, nullptr))::value;
};

// 構(gòu)造一個(gè)TryEquals在調(diào)用類型沒有合適的==時(shí)拋出異常
template<typename T, bool EqComparable = IsEqualityComparable<T>::value>
struct TryEquals {
  static bool equals(const T& x1, const T& x2)
  {
    return x1 == x2;
  }
};

class NotEqualityComparable : public std::exception {};

template<typename T>
struct TryEquals<T, false> {
  static bool equals(const T& x1, const T& x2)
  {
    throw NotEqualityComparable();
  }
};
  • 在X中實(shí)現(xiàn)equals時(shí),使用TryEquals替代operator==即可達(dá)到同樣的目的
virtual bool equals(const B<R, Args...>* fb) const override
{
  if (auto specFb = dynamic_cast<const X*> (fb))
  {
    return TryEquals<F>::equals(f, specFb->f);
  }
  return false;
}

性能考慮

  • 類型擦除同時(shí)提供了部分靜態(tài)多態(tài)和動(dòng)態(tài)多態(tài)的優(yōu)點(diǎn)探孝。使用類型擦除的泛型代碼的性能更接近于動(dòng)態(tài)多態(tài)笋婿,因?yàn)閮烧叨纪ㄟ^虛函數(shù)使用動(dòng)態(tài)分派,因此可能丟失一些靜態(tài)多態(tài)的傳統(tǒng)優(yōu)點(diǎn)顿颅,如編譯器內(nèi)聯(lián)調(diào)用的能力缸濒。雖然這種性能損失能否感知依賴于應(yīng)用程序,但通常很容易判斷粱腻”优洌考慮相對(duì)虛函數(shù)調(diào)用開銷需要執(zhí)行多少工作:如果兩者接近,類型擦除可能比靜態(tài)多態(tài)慢得多绍些,反之如果函數(shù)調(diào)用執(zhí)行大量工作捞慌,如查詢數(shù)據(jù)庫(kù)、排序容器或更新用戶接口柬批,類型擦除的開銷就算不上多大
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卿闹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子萝快,更是在濱河造成了極大的恐慌,老刑警劉巖著角,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揪漩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吏口,警方通過查閱死者的電腦和手機(jī)奄容,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來产徊,“玉大人昂勒,你說我怎么就攤上這事≈弁” “怎么了戈盈?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谆刨。 經(jīng)常有香客問我塘娶,道長(zhǎng),這世上最難降的妖魔是什么痊夭? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任刁岸,我火速辦了婚禮,結(jié)果婚禮上她我,老公的妹妹穿的比我還像新娘虹曙。我一直安慰自己迫横,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布酝碳。 她就那樣靜靜地躺著矾踱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪击敌。 梳的紋絲不亂的頭發(fā)上介返,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音沃斤,去河邊找鬼圣蝎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛衡瓶,可吹牛的內(nèi)容都是我干的徘公。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼哮针,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼关面!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起十厢,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤等太,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蛮放,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缩抡,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年包颁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞻想。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娩嚼,死狀恐怖蘑险,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岳悟,我是刑警寧澤佃迄,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站贵少,受9級(jí)特大地震影響和屎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜春瞬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一柴信、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宽气,春花似錦随常、人聲如沸潜沦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)唆鸡。三九已至,卻和暖如春枣察,著一層夾襖步出監(jiān)牢的瞬間争占,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工序目, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留臂痕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓猿涨,卻偏偏與公主長(zhǎng)得像握童,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叛赚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 竹外桃花三兩枝澡绩,春江水暖鴨先知。 ???????????????? -蘇軾 Algorithm Descripti...
    三哥_閱讀 322評(píng)論 0 0
  • 回調(diào) 回調(diào)的含義是:對(duì)一個(gè)庫(kù)俺附,用戶希望庫(kù)能夠調(diào)用用戶自定義的某些函數(shù)肥卡,這種調(diào)用稱為回調(diào)。C++中用于回調(diào)的類型統(tǒng)稱...
    奇點(diǎn)創(chuàng)客閱讀 223評(píng)論 0 0
  • 按值傳遞 按值傳遞實(shí)參時(shí)事镣,原則上會(huì)拷貝每個(gè)實(shí)參步鉴,對(duì)于類通常還需要調(diào)用拷貝構(gòu)造函數(shù)。調(diào)用拷貝構(gòu)造函數(shù)可能開銷很大蛮浑,但...
    奇點(diǎn)創(chuàng)客閱讀 231評(píng)論 0 0
  • C++ lambda表達(dá)式與函數(shù)對(duì)象 lambda表達(dá)式是C++11中引入的一項(xiàng)新技術(shù),利用lambda表達(dá)式可以...
    小白將閱讀 85,194評(píng)論 15 118
  • 01 一個(gè)實(shí)例:累加一個(gè)序列 1.1 Fixed Traits 上述代碼的問題是只嚣,對(duì)于 char 類型希望計(jì)算對(duì)應(yīng)...
    奇點(diǎn)創(chuàng)客閱讀 289評(píng)論 0 0