Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C多态库Proxy学习笔记一 #11901

Open
guevara opened this issue Nov 25, 2024 · 0 comments
Open

C多态库Proxy学习笔记一 #11901

guevara opened this issue Nov 25, 2024 · 0 comments

Comments

@guevara
Copy link
Owner

guevara commented Nov 25, 2024

C++多态库Proxy学习笔记(一)



https://ift.tt/sLKtPNM



Liu Peiqi


本文是C++20标准下微软提供实现多态能力的头文件库的学习笔记。当前网络上虽然有很多相关文章了,但是本文侧重点除了学习库怎么用以外,还深入探讨Proxy库用到的C++14、C++17、C++20等的特性,学习Proxy是怎么实现的。

常规C++的多态实现

实现容器基类,然后派生动态数组和集合类型:

class Container{
public:
    virtual void insert(...);
    virtual void remove(...);
    virtual ~Container();
}

class Vector:Container{
public:
virtual void insert(...) override;
virtual void remove(...) override;
virtual ~Vector();
}

class Set:Container{
public:
virtual void insert(...) override;
virtual void remove(...) override;
virtual ~Set();
}

在使用时,我们可以就针对Container操作就可以了。

void do_some_thing(Container* c){
    c->insert(...);
    if(...){
        c->remove(...);
    }
}

int main(){
Vector v;
Set s;
do_smoe_thing(&v);
do_smoe_thing(&s);
return 0;
}

CPP这个是很简单的例子。之前我还长时间维护过芯片行业的电路仿真引擎,它最早是上个世纪七八十年代伯克利大学设计的C语言项目。项目巨大,维护复杂,在一众面向对象语言出现前,面向对象和多态的思想已经在C语言中开始尝试了。它是这么处理多态的:

struct Container{
    void (*insert_ptr)(...);
    void (*remove_ptr)(...);
}

struct Vector{
void (*insert_ptr)(...);
void (*remove_ptr)(...);
void insert(...);
void remove(...);
void init(){
insert_ptr = insert;
remove_ptr = remove;
}
}

struct Set{
Container c;
void insert(...);
void remove(...);
void init(){
c.insert_ptr = insert;
c.remove_ptr = remove;
}
}

void do_some_thing(Container* c){
c->insert(...);
if(...){
c->remove(...)
}
}

int main(){
Vector v;
v.init();
Set s;
s.init();
do_some_thing((Container*)(&v));
do_some_thing(&s.c);
return 0;
}

上面在实现Vector和Set的时候分别提供了两种写法,原因是在上述引擎代码中都有体现,它通过C语言明确的内存布局,利用强制类型转换和函数指针实现多态能力。当然这么实现太灵活、太危险,稍稍大意就会写错漏写,或者成员函数顺序错误。

CPP模板

上面东拉西扯一些入门知识,主要是铺垫多态实现的思路,现在很多人发出了思想挑战:多态不一定要通过虚函数、虚表来实现。比如CPP的模板编程也可以:

template<class Container>
void do_something(Container& c){
    c.insert(...);
    if(...){
        c.remove(...);
    }
}

#include<vector>
#include<set>

int main(){
std::vector<int> v;
std::set<int> s;
do_something(v);
do_something(s);
return 0;
}

上面代码编译无法通过,主要是表达思路,编译器生成多个重载函数,给两次调用实现了两种不同的操作,静态的实现了多态逻辑。但是这个还是不够的,很多时候我们需要多态不仅仅是静态的,还需要动态的,它们还要是统一的类型,要能放到一个数组中的。模板编程也不是不行:

#include <functional>
struct ContainerFacade{
    std::function<void(...)> insert;
    std::function<void(...)> remove;
}

void do_something(std::vector<ContainerFacade> containers){
for(auto c : containers){
c.insert(...);
if(...){
c.remove(...);
}
}
}

#include <vector>
#include <set>

int main(){
std::vector<int> v;
std::set<int> s;
do_something({ContainerFacade{std::bind(&std::vector<int>::emplace_back,
&v, std::placeholder::_1),
std::bind(&std::vector<int>::remove,
&v, std::placeholder::_1)},
ContainerFacade{std::bind(&std::set<int>::insert,
&v, std::placeholder::_1),
std::bind(&std::set<int>::remove,
&v, std::placeholder::_1)}});
return 0;
}

利用CPP现代语言特性和语法糖,std::funcation和std::bind动态绑定,实现运行时多态。诚如网友的评价: “代码丑到这个样子,性能一定快到飞起吧!”。

其实这个实现跟第一节第二个例子,C语言的多态实现是一个思路。我们要的多态,不一定就是个继承来的对象,而是方便的统一的抽象,可以放到一起,调用相同逻辑或类似逻辑的具体函数就行。C语言的实现太过灵活,抹除了类型型别,在实践过程中对使用者的要求太过苛刻。传统的继承方法常常讨论is a关系,Cat is a AnimalDog is a Animal极为繁琐不说,在工程实践中,某个类通常是很多基类的子类。CPP可以多继承,甚至处理菱形继承还搞出了虚继承。其他语言则很多直接禁止了多继承,再构造出接口的概念,最终实现的还是多继承。使用时dynamic cast类型转换漫天飞不说,如果已经设计好的库的类型不满足新的接口逻辑,那么还要再通过派生和继承新接口逻辑来调整,最后产生更多的隐晦的类型。

既然如此,那为什么不放弃继承关系,回到原始需求,类型只要具备特定接口的能力,能被统一的方法调用,是不是就好了?

非侵入式的运行时多态,外观和接口只是特定几个类型中的某几个函数的聚合。

上述CPP模板的案例符合这些特征,但是过程太过复杂繁琐,微软的Proxy库则提供了相对简单的实现。

Proxy

#include <proxy/proxy.h>

struct insert : pro::dispatch<void(...)>{
template <class T>
void operator()(T& self, ...){self.insert(...);}
};

struct ContainerFacade : pro::facade<insert>{};

void do_something(pro::proxy<ContainerFacade> c){
c.invoke<insert>(...);
}

#include <set>

int main(){
std::vector<int> v;
do_something(pro::make_proxy<ContainerFacade>(v));
}

当然后面还有简化版,通过宏来代替上面一长串外观和仿函数定义,调用处也简单一点,不用那个invoke了:

#include <proxy/proxy.h>

namespace spec {
PRO_DEF_MEMBER_DISPATCH(insert, void(...));
PRO_DEF_FACADE(ContainerFacade, insert);
}

void do_something(pro::proxy<spec::ContainerFacade> c){
c.insert(...);
}

#include <set>

int main(){
std::set<int> v;
do_something(&v);
}

“代码丑到这个样子,性能一定快到飞起吧!”。







via LPQ-Blog

November 25, 2024 at 12:02PM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant