惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
Forbes - Security
Forbes - Security
The Last Watchdog
The Last Watchdog
TaoSecurity Blog
TaoSecurity Blog
Schneier on Security
Schneier on Security
SecWiki News
SecWiki News
V
Vulnerabilities – Threatpost
Project Zero
Project Zero
O
OpenAI News
W
WeLiveSecurity
Security Archives - TechRepublic
Security Archives - TechRepublic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Hacker News: Front Page
Cisco Talos Blog
Cisco Talos Blog
Spread Privacy
Spread Privacy
Help Net Security
Help Net Security
P
Privacy & Cybersecurity Law Blog
K
Kaspersky official blog
S
Security @ Cisco Blogs
Latest news
Latest news
AWS News Blog
AWS News Blog
U
Unit 42
Martin Fowler
Martin Fowler
阮一峰的网络日志
阮一峰的网络日志
S
Secure Thoughts
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Scott Helme
Scott Helme
博客园 - 司徒正美
B
Blog RSS Feed
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
D
Docker
Google Online Security Blog
Google Online Security Blog
Jina AI
Jina AI
aimingoo的专栏
aimingoo的专栏
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Last Week in AI
Last Week in AI
月光博客
月光博客
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
Attack and Defense Labs
Attack and Defense Labs
小众软件
小众软件

核桃的炼金工坊

2023 韩国游记 C++23: Flat Containers Deducing This Stateful Metaprogramming 推し、燃ゆ 2020 总结 搞了个 C++ 构建系统 软件设计哲学(NOTE) Paxos Note 关于 cpp 可见性的黑魔法后门 一个关于 private member function detect 的 SFINAE 模板 User-defined conversion and Copy elision VIM and Latex Compare Between CRTP and Virtual Interface in C++ Compile Time Reflection in C++11 C++11内存模型 在C++17中的部分新特性 Const Reference of Pointer
Customization Point Object
Hawtian Wang · 2022-09-06 · via 核桃的炼金工坊

标题是要聊一下 C++20 带来的一些新的很有意思的新机制,或者说是新轮子。 用来解决库函数或者一些通用函数定制用户类型的行为的抽象。 比如,我现在要实现一个通用的算法:

template<typename T>
void foo(T& vec);

我可能需要我的参数 vec 的类型 T。可以拿到他的两个对应迭代器和大小。(这里只是举个例子) 在这个时候,就是我的通用函数需要定制用户类型的行为。或者说,需要用户的类型提供我需要的能力。 对于这样的每一个对用户能力的要求,被称为一个 定制点

第一个办法显然是继承嘛,考虑这个例子:

class ConnectionBase {
  void on_buffer_received(std::span<char> buffer) {
    if (handle_buffer(buffer)) {
      // ...
    }
  }

 protected:
  virtual bool handle_buffer(std::span<char> buffer) = 0;
};

class TlsConnection : public ConnectionBase {
 protected:
  bool handle_buffer(std::span<char> buffer) {
    // ssl...
  }
};

基类的纯虚函数 ConnectionBase::handle_buffer(std::span<char> buffer) 就是一个定制点。它在这里需要用户类型,也就是它的子类定义如何处理收到的 buffer 的行为。

还是考虑上面那个例子:

template<typename D>
class ConnectionBase {
  void on_buffer_received(std::span<char> buffer) {
    if (static_cast<D*>(this)->handle_buffer(buffer)) {
      // ...
    }
  }
};

class TlsConnection : public ConnectionBase<TlsConnection> {
  bool handle_buffer(std::span<char> buffer) {
    // ssl...
  }
};

这样基类可以直接访问子类的功能和实现,而且绕过了依赖虚表实现的多态调用有更好的性能。 缺点是在这样的情况下,定制点变的非常不明显,他可以不依靠任何的在基类中的声明来表达子类需要实现哪些定制点

依旧是上面那个例子:

namespace tls {
  class TlsConnection {};

  bool handle_buffer(TlsConnection* conn, std::span<char> buffer); // #1
}

namespace tcp {
  class TcpConnection {};

  bool handle_buffer(TcpConnection* conn, std::span<char> buffer); // #2
}

tls::TlsConnection* tls;
tcp::TcpConnection* tcp;

handle_buffer(tls);  // #1
handle_buffer(tcp);  // #2

在这种方案下,通过在对应参数的 namespace 来实现对应的定制功能,然后通过 ADL 来找到正确的实现。 在这样的方案下带来的一个问题就是,对于这个 handle_buffer,我们使用带 namespace 和不带 namespace 的版本可能会带来不一样的结果。

在 C++20 里,其实 ranges 也面临了类似的问题,需要兼容各种类型的容器就要为每个类型的容器定制对应的功能。ranges 给出的方案就是 CPO,customization point object。

A customization point object is a function object with a literal class type that interacts with program-defined types while enforcing semantic requirements on that interaction.

CPO 有很好的泛型的兼容性,而且也可以弥补上面 ADL 所提到的问题。 考虑一个取一个任意类型容器的 begin iterator 的问题:

namespace _Begin {
  class _Cpo {
    enum class _St { _None, _Array, _Member, _Non_member };

    template <class _Ty>
    static _CONSTEVAL _Choice_t<_St> _Choose() noexcept {
      if constexpr (is_array_v<remove_reference_t<_Ty>>) {
        return {_St::_Array, true};
      } else if constexpr (_Has_member<_Ty>) {
        return {_St::_Member, noexcept(_Fake_decay_copy(_STD declval<_Ty>().begin()))};
      } else if constexpr (_Has_ADL<_Ty>) {
        return {_St::_Non_member, noexcept(_Fake_decay_copy(begin(_STD declval<_Ty>())))};
      } else {
        return {_St::_None};
      }
    }

    template <class _Ty>
    static constexpr _Choice_t<_St> _Choice = _Choose<_Ty>();

   public:
     template <_Should_range_access _Ty>
       requires (_Choice<_Ty&>._Strategy != _St::_None)
      _NODISCARD constexpr auto operator()(_Ty&& _Val) const {
          constexpr _St _Strat = _Choice<_Ty&>._Strategy;
          if constexpr (_Strat == _St::_Array) {
            return _Val;
          } else if constexpr (_Strat == _St::_Member) {
            return _Val.begin();  // #1: via member function
          } else if constexpr (_Strat == _St::_Non_member) {
            return begin(_Val);   // #2: via ADL
          } else {
            static_assert(_Always_false<_Ty>, "Should be unreachable");
          }
        }
  };
}

inline namespace _Cpos {
  inline constexpr _Begin::_Cpo begin;
}

这个是 ranges 库里面的 begin 的实现,我只留下了为了说明问题的关键的部分。在这个实现里,用 if constexpr 处理了类型选择的大部分的逻辑。对于对一个容器类型求 begin 的操作被分成了三种情况,数组、成员函数、非成员函数。数组是 builtin 的类型就不聊了嘛;成员函数的调用类似上面的 CRTP 的用法,因为通过模板是明确拿到当前容器的类型的,可以直接调用对应自定义类型的成员;非成员函数的调用因为是直接用的 begin 这个名字,所以这里使用了上面的 ADL 的方式来找到对应容器自定义类型正确的处理函数。

为什么是一个 function object 而不是一个模板函数,是因为 function object 在这个情况下是不会被应用 ADL 的,也就是说不会被 ADL 找到的,避免了在 #2 那个地方的 ADL 递归调用的问题。


references:

  1. C++ Draft: customization point object
  2. Cppreference: ranges
  3. Niebloids and Customization Point Objects