Boost.ASIO源碼:service_registry::use_service()詳解以及相關type_traits解析

這都是神仙寫的代碼吧

沒什麼,這個標題只是忍不住表達一下對ASIO的驚歎。
曾經看《STL源碼剖析》對裏面的type_traits的設計驚爲天人,沒想到看ASIO庫的時候又看到了同樣的設計模式,雖然對於C++功底還不深的我來說看起來十分的費勁,但我還是決定好好的自己理解一遍,並把它記錄下來。由於不喜歡黑盒法,所以幾乎每個函數我都會跳進去瞅瞅,所以雖然use_service邏輯不復雜,但這裏介紹依舊會很細。

use_service()的功能與內部邏輯

首先,解釋一下,use_service存在於execution_context.hpp中,是一個全局模板函數,用於得到一個指定type的Service對象。以下爲函數聲明:

template <typename Service> Service& use_service(execution_context&);
template <typename Service> Service& use_service(io_context&);

Service是ASIO中的一種概念,如scheduler和epoll_reactor都是Service,其實Service具體是啥我也沒有理解得特別深 ,但它們都有一個明顯的共同點,就是都繼承自execution_context::service

下面回到use_service函數上來,這裏以一個use_service的使用情況來開始說明。

// scheduler類的一個成員函數
void scheduler::init_task()
{
  mutex::scoped_lock lock(mutex_);
  if (!shutdown_ && !task_)
  {

    task_ = &use_service<reactor>(this->context());  //關注這一行就行了
    /* typedef class epoll_reactor reactor; */  // 這是在一個遙遠的山卡拉角落對於reactor的定義
    
    op_queue_.push(&task_operation_);
    wake_one_thread_and_unlock(lock);
  }
}

先說明作用,那一句能得到一個reactor實例並賦給task_,這個reactor就是epoll_reactor類,它也是個execution_context::service的子類。
進入這個use_service函數內部:

template <typename Service>
inline Service& use_service(execution_context& e)
{
  // Check that Service meets the necessary type requirements.
  (void)static_cast<execution_context::service*>(static_cast<Service*>(0));

  return e.service_registry_->template use_service<Service>();
}

對於中間那一行看起來執行不了任何功能的連續強轉剛開始我也是一頭霧水,後來才發現實際上這行就是保證傳進來的這個模板類型Service,是execution_context::service的子類,如果這個Service實際上是vector或者double之類的類型,那麼這一句就會報編譯錯誤。
嗯很巧妙的寫法,看起來效率很高。不過爲什麼不用 template <typename Service extends execution_context::service>的寫法呢。希望有人能給我指正一下。
同時在這裏能看出來傳進來的這個execution_context對象是幹啥的了,在return 那一句可以看到它取出了這個execution_context對象的service_registry_,並轉而調用這貨的use_service。下面爲了解釋service_registry這個成員,截出了execution_context類中的兩個個片段:

private:
  // The service registry.
  boost::asio::detail::service_registry* service_registry_;
execution_context::execution_context()
  : service_registry_(new boost::asio::detail::service_registry(*this))
{
}

execution_context::~execution_context()
{
  shutdown();
  destroy();
  delete service_registry_;
}

每一個execution_context對象都與一個service_registry對象互相綁定,他們生一起生,死一起死。而這個service_registry類是用來管理所有的execution_context::service對象的,具體怎麼管理這裏不細講了,反正就是個管理器。
再進到這個service_registry的use_service方法中:

template <typename Service>
Service& service_registry::use_service(io_context& owner)
{
  execution_context::service::key key;   // 這個key對每種類型的Service是唯一的。
  init_key<Service>(key, 0);   // 根據Service類型初始化(也可以說得到)它的key。這句是關鍵!!
  factory_type factory = &service_registry::create<Service, io_context>;   // 這裏得到一個創建Service的方法
  return *static_cast<Service*>(do_use_service(key, factory, &owner));//具體創建還是不創建再說,反正這句能得到一個目標Service對象
}

這裏先不探究key長啥樣,反正知道它對每種類型的Service是唯一的就行了。init_key是關鍵,它能根據Service的類型信息得到一個唯一的key(令人驚豔的是大部分判斷邏輯都是編譯時完成,也就是type_traits思想,太6了),後面解釋內部邏輯,內部涉及到了type_traits機制。方法體內第三行得到的是一個方法指針,這個方法可以用來創建一個Service對象:

  // The type of a factory function used for creating a service instance.
  	typedef execution_context::service*(* factory_type )(void*);  // 在另一個角落對factory_type的定義
	
	// ... 十萬光年的距離

	template <typename Service, typename Owner>
	execution_context::service* service_registry::create(void* owner)
	{
	  return new Service(*static_cast<Owner*>(owner));
	}

回到service_registry的use_service函數,裏面最後一行的do_use_service方法邏輯其實非常簡單,簡單到不想貼那一坨代碼來增加我的篇幅。service_registry中維護了一個execution_context::service鏈表(其實每個execution_context::service都有個next指針),do_use_service就是到service_registry中找它裏面有沒有保存這個類型的Service對象,有就拿出來,沒有就用傳進來的factory方法創建一下,並把這個owner也就是某個execution_context(或者說io_context)給綁到這個創建出來的Service上(execution_context::service的構造函數要傳入一個execution_context對象)。

init_key()詳解

先看代碼:

	// Initalise a service's key when the key_type typedef is not available.
	template <typename Service>
	inline void service_registry::init_key(
	    execution_context::service::key& key, ...)
	{
	  init_key_from_id(key, Service::id);
	}

	// Initalise a service's key when the key_type typedef is available.
	template <typename Service>
	void service_registry::init_key(execution_context::service::key& key,
	    typename enable_if<
	      	is_base_of<typename Service::key_type, Service>::value>::type*)
	{
	  key.type_info_ = &typeid(typeid_wrapper<Service>);
	  key.id_ = 0;
	}

	// Initalise a service's key when the key_type typedef is available.
	template <typename Service>
	void service_registry::init_key_from_id(execution_context::service::key& key,
	    const service_id<Service>& /*id*/)
	{
	  key.type_info_ = &typeid(typeid_wrapper<Service>);
	  key.id_ = 0;
	}

這裏init_key方法有2個重載,如果Service有定義Service::key_type這個字段,就走那個參數一大坨的重載,反之,就走那個參數列表比較短的重載。如果沒有定義Service::key_type,就會根據Service::id來初始化Service的key。
接下來稍微解釋一下這個key。execution_context::service::key就那兩個屬性:type_info_和id_。type_info_比較好理解,就是STL裏面那個type_info,描述類型信息的,type_wrapper是一個模板空類,(我猜)應該是爲了簡化typeid返回的type_info中數據。另一個id_就是一個execution_context的內部類id的對象,這個execution_context::id類是空的,也是不可複製的(繼承自noncopyable),至於用法現在還沒看懂。。。爲了方便理解再貼上key的匹配方法:

	bool service_registry::keys_match(
	    const execution_context::service::key& key1,
	    const execution_context::service::key& key2)
	{
	  if (key1.id_ && key2.id_)
	    if (key1.id_ == key2.id_)
	      return true;
	  if (key1.type_info_ && key2.type_info_)
	    if (*key1.type_info_ == *key2.type_info_)
	      return true;
	  return false;
	}

可以看出優先匹配id_,再匹配type_info_。

enable_if和is_base_of中的細節

這是我最感興趣的一部分,雖然與use_service的主要邏輯關係並不大,但還是想仔細研究下。
先把關鍵代碼再貼上來一次:

template <typename Service>
void service_registry::init_key(execution_context::service::key& key,

	// 就是這裏
    typename enable_if<
      is_base_of<typename Service::key_type, Service>::value>::type*)
      
{
  key.type_info_ = &typeid(typeid_wrapper<Service>);
  key.id_ = 0;
}

先解釋enable_if,先上代碼:

  template<bool, typename _Tp = void>
    struct enable_if 
    { };

  template<typename _Tp>
    struct enable_if<true, _Tp>
    { typedef _Tp type; };

可以看到下面那個是一個偏特化版本,其中也只有偏特化版本纔有type的定義。可以看到enable_if的調用都是typename enable_if <[一坨東西]>,如果這傳過來的[一坨東西]是個bool值,那麼實例化上面那個版本,如果傳進來的是bool以外的什麼鬼東西,就走下面那個版本。
這裏涉及到一個概念SFINAE:Substitution Failure Is Not An Error(匹配失敗不是錯誤),翻譯成普通話就是在進行模板特化的時候,會去選擇那個正確的模板,避免失敗。以我們的例子來說明,前面說過init_key有兩個版本,當我們調用init_key(key, 0)時,按道理會匹配enable_if那個版本的init_key重載,但是編譯器在根據我們傳進來的參數實例化enable_if模板時發現實例化出來的類沒有type定義(實例化出來的是上面那個enable_if版本),但我們卻倔強地用了enable_if<[一坨東西]>::type*,照理說這裏是要報錯的,但按照SFINAE原則,這不算錯,於是編譯器放棄這個函數重載版本,用另一個init_key的重載版本,於是程序得以正常運行。
接下來看is_base_of,以及它的基類integral_constant:

	template<typename _Base, typename _Derived>
	    struct is_base_of
	    : public integral_constant<bool, __is_base_of(_Base, _Derived)>
	    { };
template<typename _Tp, _Tp __v>
    struct integral_constant
    {
      static constexpr _Tp                  value = __v;
      typedef _Tp                           value_type;
      typedef integral_constant<_Tp, __v>   type;
      constexpr operator value_type() const { return value; }

      constexpr value_type operator()() const { return value; }
    };

  /// The type used as a compile-time boolean with true value.
  typedef integral_constant<bool, true>     true_type;

  /// The type used as a compile-time boolean with false value.
  typedef integral_constant<bool, false>    false_type;

這裏引用一下網上對integral_constant的解釋:用integral_constant方便地定義編譯期常量,而無需再通過enum和static const變量方式。
is_base_of實際上只用到了下面的2個偏特化版本的integral_constant:true_type和false_type,根據__is_base_of返回的值是true還是false決定is_base_of<Base, Derived>::value返回的true還是false,從而影響enable_if的實例化。
至於最關鍵的__is_base_of()函數我沒找到源碼。。。網上也找不到資料。。。不過根據我的第六感,這應該是返回_Derived是不是_Base的基類的判斷。若有對該函數知情者望告知多謝。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章