C++ Proxy 4:更方便模組化的 skill

| | 0 Comments| 14:52|
Categories:

Heresy 在去年有寫過一篇《C++ Proxy:額外定義介面的非侵入式多型架構》紀錄微軟設計的 Proxy 這個動態多型的函式庫,他的網址是:

https://github.com/microsoft/proxy

不過由於目前沒有實際使用的需求,所以後來又寫了一篇《C++ Proxy 的一些細節》就沒再管它了。

而前一陣子,微軟發布了《Announcing Proxy 4: The Next Leap in C++ Polymorphism》,宣布這個函式庫由 3 升級到 4、並提供了一些新的設計,所以這邊就稍微來看一下吧。


Skills:預先組合好的能力

Proxy 4 最大的特色,應該是新加入的「skill」這個概念。它的本質上只是 alias template、裡面還是使用本來的 add_convention

而加入這個概念的目的是讓開發者可以更簡單地組成自己需要的介面定義、並重複利用,官方目前提供了下面幾種 skill:

  • format:提供 C++20 std::format 的格式化介面
  • fmt_format:提供 {fmt} 的格式化介面
  • rtti:runtime type information、提供取得型別資訊的介面
  • as_view:允許將 proxy 物件轉換成明確表示沒有所有權的 proxy_view
  • as_weak:允許將 proxy 物件轉換成 weak_proxy(對應 std::weak_ptr
  • slim:限制僅使用單一指標大小的記憶體配置

在使用時,基本上是下面的形式:

struct Formattable : pro::facade_builder
  ::add_skill<pro::skills::format>
  ::build {};

這邊是透過 add_skill<> 來做設定,使用上算是更為方便。

下面就簡單拿兩個 skill 來當範例看一下。


skills::format

以 format 這個 skill(官方文件)來說,是告訴邊義器這個 proxy 型別可以支援 C++20 的 std::format 這個格式化輸出的函式庫;下面是一個簡單的例子:

#include <iostream>
#include <format>
 
#include "proxy.h"
 
struct Formattable : pro::facade_builder
  ::add_skill<pro::skills::format>
  ::build {};
 
int main()
{
  pro::proxy<Formattable> p = pro::make_proxy<Formattable>(123);
  std::cout << std::format("{}\n", *p); // "123"
}

這邊是定義了一個包含 pro::skills::format 這個 skill 的外觀 Formattable

在有加入這個 skill 的狀況下,就可以直接把 pro::proxy<Formattable> 拿來搭配 std::format、做格式化輸出。而如果把 add_skill<pro::skills::format> 拿掉的話,在編譯階段就會直接出現錯誤。

這邊要注意的,是型別本身還是需要有支援 std::format 才能直接使用,如果是自定義的型別就需要自己實作 std::formatter 才行(參考);下面就是把使用的型別改成 MyInt 的例子:

#include <iostream>
#include <format>
 
#include "proxy.h"
 
struct Formattable : pro::facade_builder
  ::add_skill<pro::skills::format>
  ::build {};
 
class MyInt
{
public:
  int iVal = 0;
};
 
template<>
struct std::formatter<MyInt> : std::formatter<std::string>
{
  template<typename FormatContext>
  auto format(const MyInt& v, FormatContext& ctx) const
  {
    return format_to(ctx.out(), "{}", v.iVal);
  }
};
 
int main()
{
  pro::proxy<Formattable> p = pro::make_proxy<Formattable>(MyInt{ 123 });
  std::cout << std::format("{}\n", *p); // "123"
}

這邊如果沒有定義 std::formatter<MyInt> 的話,是會編譯失敗的。

另外,這邊也還有 skills::wformat、對應寬字元的版本可以使用。

而由於微軟只有提供 std::format 的 skill,所以如果是要直接搭配 std::ostream 的話,就要自己透過 pro::operator_dispatch 來寫了。這部分官方有範例可以參考(連結)。


skills::rtti

RTTI(runtime type information)這個 skill(官方文件)是用來讓 proxy 物件可以在執行階段可以使用型別相關的函式用的。

Proxy 這邊提供了三種模式:rttiindirect_rttidirect_rtti。前兩者應該是完全相同的,在使用時會需要多透過一層 proxy_indirect_accessor 才能存取對應的功能;後者則是讓他可以直接存取,相對起來比較方便。

這邊提供了兩個對應的全域函式可以用:

下面是簡單的例子:

#include <iostream>
#include <string>
 
#include "proxy.h"
 
struct RttiAware : pro::facade_builder
  ::add_skill<pro::skills::rtti>
  ::add_skill<pro::skills::direct_rtti>
  ::build {};
 
class MyInt
{
public:
  int iVal = 0;
};
 
int main()
{
  pro::proxy<RttiAware> p = pro::make_proxy<RttiAware>(MyInt{ 123 });
  std::cout << proxy_typeid(p).name() << "\n";
  // class pro::v4::details::inplace_ptr<class MyInt>
 
  MyInt i = proxy_cast<MyInt>(*p);
  std::cout << i.iVal << "\n";
}

這邊可能要注意的,是要使用 proxy_cast() 的話,似乎除了 skills::direct_rtti 也還需要 skills::rtti 才行。


自定義 skill

要怎麼定義自己的 skill 呢?如果去看 skills::format,會發現它其實只是:

template <class FB>
using format =
    typename FB::template add_convention<details::format_dispatch,
                                         details::format_overload_t<char>>;

所以其實要按照這個方法來定義自己的 skill 其實也相當簡單~

假設本來定義了一個可以呼叫 toString()area() 兩個函式的外觀的話,可能會寫成:

PRO_DEF_MEM_DISPATCH(MemText, toString);
PRO_DEF_MEM_DISPATCH(MemArea, area);
 
struct ShapeProxy : pro::facade_builder
  ::add_convention<MemText, std::string() const>
  ::add_convention<MemArea, double() const>
  ::support_copy<pro::constraint_level::nontrivial>
  ::build {};

而如果想要把這兩個函式包成 skill 的話,可以另外建立一個 shape.hpp 的檔案,內容如下:

#pragma once
 
#include <proxy.h>
 
PRO_DEF_MEM_DISPATCH(MemText, toString);
PRO_DEF_MEM_DISPATCH(MemArea, area);
 
template <class FB>
using MyShape = typename FB
    ::template add_convention<MemText, std::string() const>
    ::template add_convention<MemArea, double() const>;

如此一來,ShapeProxy 就可以改寫成:

struct ShapeProxy : pro::facade_builder
  ::add_skill<MyShape>
  ::support_copy<pro::constraint_level::nontrivial>
  ::build {};

當然啦,如果只用一次的話,這個寫法基本上沒什麼用;但是如果同樣的函式組成會用在很多地方的話,那就算是可以變方便很多了~

雖然說本來應該也可以透過 add_facade<> 來做到類似的事情,不過 skill 應該算是更明確的概念和設計吧?


大概就先這樣了。

基本上 proxy 4 應該是還有些新東西可以研究,不過由於 Heresy 目前也沒有真的在使用,所以就先不繼續研究下去了,或許等哪天真地要用到的時候,再繼續來研究吧~

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *