快速上手 Microsoft Proxy 库
2025-05-25 20:11:24

介绍

最近了解到了一个微软开源的 C++ 库Proxy,并称其为下一代多态库,旨在解决 C++ 传统虚函数和继承带来的问题。
GitHub:https://github.com/microsoft/proxy


简单点说,这个库实现了类似 go 语言中的 interface。直接看一个例子就懂了,传统C++风格的多态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct animal
{
virtual std::string name() const = 0;
};

struct dog : public animal
{
std::string name() const override
{
return "dog";
}
};

struct cat : public animal
{
std::string name() const override
{
return "cat";
}
};

void print_name(animal& a)
{
std::cout << a.name() << std::endl;
}

int main(int argc, char** argv)
{
dog d;
print_name(d);

cat c;
print_name(c);

return 0;
}

用了Proxy后的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <proxy.h>

struct dog
{
std::string name() const
{
return "dog";
}
};

struct cat
{
std::string name() const
{
return "cat";
}
};

// 定义成员方法
PRO_DEF_MEM_DISPATCH(MemName, name);

// 理解为接口即可
struct AnimalFacade : pro::facade_builder
::add_convention<MemName, std::string() const>
::build
{
};

void print_name(const pro::proxy<AnimalFacade>& a)
{
std::cout << a->name() << std::endl;
}

int main(int argc, char** argv)
{
const auto d = pro::make_proxy<AnimalFacade, dog>();
print_name(d);

const auto c = pro::make_proxy<AnimalFacade, cat>();
print_name(c);
return 0;
}

区别很明显:dogcat没有祖先类,也没有虚函数。只需要满足Facade定义即可,成功干掉了虚函数。

构造方法

空接口

1
2
pro::proxy<AnimalFacade> empty_facade;
assert(empty_facade == nullptr);

构造新实例

1
2
3
pro::proxy<AnimalFacade> p1 = pro::make_proxy<AnimalFacade, dog>();
pro::proxy<AnimalFacade> p2 = pro::make_proxy_inplace<AnimalFacade, dog>();
pro::proxy<AnimalFacade> p3 = pro::make_proxy_shared<AnimalFacade, dog>();

这三种构造方式的主要区别在于内存分配方式生命周期管理

pro::make_proxy

  • 堆分配:在堆上创建 dog 对象
  • 独占所有权:类似 std::unique_ptr
  • 移动语义:只能移动,不能拷贝(除非指定 support_copy

pro::make_proxy_inplace

  • 栈内分配:在 proxy 对象内部直接存储 dog
  • 无堆分配:性能更好,避免动态内存分配
  • 大小限制:对象必须足够小以适应 proxy 的内部存储

pro::make_proxy_shared

  • 共享所有权:类似 std::shared_ptr
  • 引用计数:多个 proxy 可以共享同一个对象
  • 自动销毁:当最后一个引用被销毁时,对象才被销毁

简短示例说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void demonstrate_differences() {
// 1. 堆分配,独占所有权
auto p1 = pro::make_proxy<AnimalFacade, dog>();
// auto p1_copy = p1; // 错误:无法拷贝(除非支持 support_copy)

// 2. 栈内分配,无堆开销
auto p2 = pro::make_proxy_inplace<AnimalFacade, dog>();
// 性能最佳,但对象大小有限制

// 3. 共享所有权,可以安全拷贝
auto p3 = pro::make_proxy_shared<AnimalFacade, dog>();
auto p3_copy = p3; // 正确:共享同一个对象

std::cout << "p3 和 p3_copy 指向同一个对象" << std::endl;
}

选择建议

  • 性能优先且对象较小 → make_proxy_inplace
  • 需要共享make_proxy_shared
  • 一般情况make_proxy

拷贝 support_copy

默认情况下是禁止 proxy 之间拷贝的

1
2
3
auto p1 = pro::make_proxy<AnimalFacade, cat>();
auto p2 = pro::make_proxy<AnimalFacade, dog>();
p1 = p2; // error

除非在facade定义时显示设置允许拷贝

1
2
3
4
5
6
7
8
struct AnimalFacade : pro::facade_builder
::add_convention<MemName, std::string() const>
::add_convention<MemAge, int() const>
::add_convention<MemSetAge, void(int)>
::support_copy<pro::constraint_level::nontrivial> // 非平凡拷贝
::build
{
};

support_copy 支持四个不同的 constraint_level 选项,它们定义了对象拷贝能力的不同约束级别:

1. constraint_level::none

  • 含义:不支持拷贝
  • 特点
    • 禁止拷贝操作
    • 这是默认级别(如果不指定 support_copy
    • 适用于只移动(move-only)的类型

2. constraint_level::nontrivial

  • 含义:支持非平凡拷贝
  • 特点
    • 要求类型具有拷贝构造函数(std::is_copy_constructible_v<T>
    • 允许有用户定义的拷贝构造函数
    • 拷贝操作可能抛出异常
    • 适用于大多数自定义类型

3. constraint_level::nothrow

  • 含义:支持无异常拷贝
  • 特点
    • 要求类型具有无异常拷贝构造函数(std::is_nothrow_copy_constructible_v<T>
    • 拷贝操作保证不抛出异常
    • 提供更强的异常安全保证

4. constraint_level::trivial

  • 含义:支持平凡拷贝
  • 特点
    • 要求类型具有平凡拷贝构造函数和平凡析构函数
    • std::is_trivially_copy_constructible_v<T>std::is_trivially_destructible_v<T>
    • 最高效的拷贝方式,通常可以使用 memcpy
    • 适用于 POD 类型和简单结构

组合接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ReadFacade : pro::facade_builder
::add_convention<MemRead, void()>
::build
{
};

struct WriteFacade : pro::facade_builder
::add_convention<MemWrite, void()>
::build
{
};

struct FileFacade : pro::facade_builder
::add_facade<ReadFacade>
::add_facade<WriteFacade>
::build
{
};

默认情况下,接口无法向上转换

1
2
auto p1 = pro::make_proxy<FileFacade, dog>();
pro::proxy<ReadFacade> p2 = p1; // error

需要显示指定add_facade第2个参数为true

1
2
3
4
5
6
struct FileFacade : pro::facade_builder
::add_facade<ReadFacade, true>
::add_facade<WriteFacade, true>
::build
{
};

要注意,转换时的拷贝行为

1. make_proxy - 会发生拷贝

1
2
auto p1 = pro::make_proxy<FileFacade, dog>();  // 堆分配,独占所有权
pro::proxy<ReadFacade> p2 = p1; // ❌ 发生拷贝!创建新的 dog 对象

2. make_proxy_inplace - 会发生拷贝

1
2
auto p1 = pro::make_proxy_inplace<FileFacade, dog>();  // 栈内分配
pro::proxy<ReadFacade> p2 = p1; // ❌ 发生拷贝!创建新的 dog 对象

3. make_proxy_shared - 不会拷贝

1
2
auto p1 = pro::make_proxy_shared<FileFacade, dog>();  // 共享所有权
pro::proxy<ReadFacade> p2 = p1; // ✅ 不拷贝!共享同一个 dog 对象

总结

  • make_proxy:堆上分配,类似std::unique_ptr,独占所有权,默认只许移动。但可以设置support_copy允许拷贝。
  • make_proxy_inplace:栈上分配,类似std::unique_ptr,独占所有权,默认只许移动。可以显示设置support_copy允许拷贝。
  • make_proxy_shared:堆上分配,类似std::shared_ptr,共享所有权,不发生拷贝,只增加引用计数。
上一页
2025-05-25 20:11:24