您是否希望简化C ++中多态对象的终身管理和维护?
您是否想像Java或C#这样的GC语言一样轻松地以C ++编写多态代码,而不会牺牲性能?
您是否尝试过C ++中的其他多态编程库,但发现它们不足?
如果是这样,这个库适合您。
“代理”是一个现代的C ++库,可帮助您无需继承而使用多态性(一种互换使用不同类型的对象的方式)。
“代理”是由Microsoft工程师创建的,自2022年以来一直在Windows操作系统中使用。多年来,使用继承一直是C ++中多态性的主要方法。但是,像Rust这样的新编程语言提供了更好的方法。我们已经提高了对面向对象的编程的理解,并决定使用C ++中的指针作为“代理”的基础。具体而言,“代理”库的设计为:
请参阅代理的常见问题以获取更多背景,并参考规格以获取更多技术细节。
“代理”是仅标题的C ++ 20库。要使用库,请确保您的编译器满足最低要求,并在源代码中包括标头文件代理。另外,您可以通过搜索“代理”来通过VCPKG或CONAN安装库(请参阅vcpkg.io和conan.io)。
让我们开始以下“ Hello World”示例:
# include < iostream >
# include < string >
# include " proxy.h "
struct Streamable : pro::facade_builder
::add_convention<pro::operator_dispatch< " << " , true >, std::ostream&(std::ostream& out) const >
::build {};
int main () {
std::string str = " Hello World " ;
pro::proxy<Streamable> p1 = &str;
std::cout << " p1 = " << *p1 << " n " ; // Prints: "p1 = Hello World"
pro::proxy<Streamable> p2 = std::make_unique< int >( 123 );
std::cout << " p2 = " << *p2 << " n " ; // Prints: "p2 = 123"
pro::proxy<Streamable> p3 = pro::make_proxy<Streamable>( 3.14 );
std::cout << " p3 = " << *p3 << " n " ; // Prints: "p3 = 3.14"
}这是一个逐步的解释:
#include <iostream> :for std::cout 。
#include <string> :for std::string 。
#include "proxy.h" :用于“代理”库。库的大多数设施都在命名空间pro中定义。如果库是通过VCPKG或Conan消费的,则应将此行更改为#include <proxy/proxy.h> 。
struct Streamable : pro::facade_builder ... ::build {} :定义一个外面类型Streamable 。正式定义为亵渎要求的“立面”一词是“代理”库模型运行时抽象的方式。具体来说,
pro::facade_builder :提供在编译时构建外墙类型的能力。add_convention :在构建上下文中添加了一个由“调度”和几个“过载”定义的广义“调用约定”。pro::operator_dispatch <"<<", true> :指定操作员的调度<< extrendies <<主操作数( proxy )在右侧(由第二个模板参数true指定)。请注意,“代理”库中的多态性由表达式而不是成员函数定义,这与C ++虚拟函数或其他OOP语言不同。std::ostream&(std::ostream& out) const :呼叫约定的签名,类似于std::move_only_function 。 const指定主要操作数为const 。build :将上下文构建为立面类型。 pro::proxy <Streamable> p1 = &str :从std::string的原始指针中创建一个proxy对象。 p1行为就像是原始指针,并且没有基础性std::string的所有权。如果str的寿命在p1之前结束,则p1将悬挂。
std::cout << *p1 :这就是它的工作方式。它打印“ Hello World”,因为呼叫惯例是在立面Streamable中定义的,因此它好像通过调用std::cout << str 。
pro::proxy <Streamable> p2 = std::make_unique <int>(123) :创建一个std::unique_ptr <int>并转换为proxy 。与p1不同, p2具有基础int的所有权,因为它是从std::unique_ptr的值实例化的,并且当p2被销毁时,它将称为std::unique_ptr的distructor,而p1没有基础int的所有权,因为它是从原始指针中实现的。 p1和p2是相同类型的pro::proxy<Streamable> ,这意味着您可以具有返回pro::proxy<Streamable>函数,而无需将有关实现详细信息的任何信息曝光。
std::cout << *p2 :打印“ 123”毫不奇怪。
pro::proxy <Streamable> p3 = pro::make_proxy <Streamable>(3.14) :从double创建proxy而不指定基础指针类型。具体来说,
p2相似, p3也具有基本double价值的所有权,但可以有效地避免堆分配。double )很小(在主要的32或64位平台上),因此pro::make_proxy在编译时意识到了事实,并返回pro::make_proxy_inplace ,这保证了不加重分配。std::function和其他现有多态性包装器不同。 std::cout << *p3 :打印“ 3.14”,毫不奇怪。
当main回报时, p2和p3将破坏基础对象,而p1无需执行任何操作,因为它拥有一个原始的指针,该指针没有基础std::string的所有权。
除了上一个示例中证明的运算符表达式外,该库还支持C ++中的几乎所有形式的表达式,并且可以使它们成为多态性。具体来说,
PRO_DEF_MEM_DISPATCH :定义成员函数调用表达式的调度类型。PRO_DEF_FREE_DISPATCH :为免费功能调用表达式定义调度类型。pro::operator_dispatch :用于操作员表达式的调度类型。pro::conversion_dispatch :转换表达式的调度类型。请注意,某些设施是作为宏提供的,因为当今的C ++模板不支持使用任意名称生成函数。这是使成员函数呼叫表达式多态的另一个示例:
# include < iostream >
# include < sstream >
# include " proxy.h "
PRO_DEF_MEM_DISPATCH (MemDraw, Draw);
PRO_DEF_MEM_DISPATCH (MemArea, Area);
struct Drawable : pro::facade_builder
::add_convention<MemDraw, void (std::ostream& output)>
::add_convention<MemArea, double () noexcept >
::support_copy<pro::constraint_level::nontrivial>
::build {};
class Rectangle {
public:
Rectangle ( double width, double height) : width_(width), height_(height) {}
Rectangle ( const Rectangle&) = default ;
void Draw (std::ostream& out) const {
out << " {Rectangle: width = " << width_ << " , height = " << height_ << " } " ;
}
double Area () const noexcept { return width_ * height_; }
private:
double width_;
double height_;
};
std::string PrintDrawableToString (pro::proxy<Drawable> p) {
std::stringstream result;
result << " entity = " ;
p-> Draw (result);
result << " , area = " << p-> Area ();
return std::move (result). str ();
}
int main () {
pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>( 3 , 5 );
std::string str = PrintDrawableToString (p);
std::cout << str << " n " ; // Prints: "entity = {Rectangle: width = 3, height = 5}, area = 15"
}这是一个逐步的解释:
#include <iostream> :for std::cout 。#include <sstream> :对于std::stringstream 。#include "proxy.h" :用于“代理”库。PRO_DEF_MEM_DISPATCH (MemDraw, Draw) :定义调用类型MemDraw用于呼叫成员函数Draw表达式。PRO_DEF_MEM_DISPATCH (MemArea, Area) :定义调用类型的MemArea ,用于呼叫成员功能Area的表达式。struct Drawable : pro::facade_builder ... ::build {} :定义Drawable外观类型。具体来说,add_convention :在构建上下文中添加呼叫约定。support_copy < pro::constraint_level ::nontrivial> :指定基础指针类型应复制,这也使结果proxy类型可复制。class Rectangle : Drawable的实现。PrintDrawableToString :将Drawable可将其转换为std::string 。请注意,这是一个函数而不是功能模板,这意味着它可以在较大的构建系统中生成ABI。pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5) :创建一个包含Rectangle proxy<Drawable>对象。std::string str = PrintDrawableToString(p) :将p转换为std::string ,隐式创建p的副本。std::cout << str :打印字符串。“代理”库是C ++中运行时多态性的独立解决方案。规格中还有许多其他功能。除了上述功能外,这里还有基于用户反馈的最受欢迎功能的策划列表:
facade_builder::add_convention比上面所示的功能更强大。它可以采用任何数量的过载类型(正式地,符合ProVerload要求的任何类型),并在调用proxy时执行标准的超负荷分辨率。facade_builder::add_facade可以灵活地组成不同的抽象。PRO_DEF_WEAK_DISPATCH定义“弱派遣”。allocate_proxy能够使用任何自定义分配器的值创建proxy 。在C ++ 11中, std::function和std::packaged_task具有构造函数,可以接受自定义分配器进行性能调整,但是在C ++ 17中删除了这些构造器,因为“语义是不清楚的,并且在类型的上下文中存储分配器存在技术问题,然后将其分配给该分配的任何分配,以便在复制分配过程中恢复任何分配”。这些问题不适用于allocate_proxy 。facade_builder为约束配置提供了全面支持,包括内存布局(通过restrict_layout ),可复制性(通过support_copy ),可重新定位性(通过support_relocation )和可破坏性(通过support_destruction )。proxy支持基于类型的运行时查询的基于类型的编译时间反射。有关更多详细信息,请参阅facade_builder::add_reflection和功能模板proxy_reflect 。 | 家庭 | 最低版本 | 必需的标志 |
|---|---|---|
| 海湾合作委员会 | 13.1 | -std = C ++ 20 |
| 铛 | 15.0.0 | -std = C ++ 20 |
| MSVC | 19.31 | /std:C ++ 20 |
| NVIDIA HPC | 24.1 | -std = C ++ 20 |
git clone https://github.com/microsoft/proxy.git
cd proxy
cmake -B build
cmake --build build -j
ctest --test-dir build -j
该项目欢迎贡献和建议。大多数捐款要求您同意撰写贡献者许可协议(CLA),宣布您有权并实际上授予我们使用您的贡献的权利。有关详细信息,请访问https://cla.opensource.microsoft.com。
当您提交拉动请求时,CLA机器人将自动确定您是否需要提供CLA并适当装饰PR(例如状态检查,评论)。只需按照机器人提供的说明即可。您只需要使用我们的CLA在所有存储库中进行一次。
该项目采用了Microsoft开源的行为代码。有关更多信息,请参见《行为守则常见问题守则》或与其他问题或评论联系[email protected]。
该项目可能包含用于项目,产品或服务的商标或徽标。 Microsoft商标或徽标的授权使用受到了Microsoft的商标和品牌准则的约束。在此项目的修改版本中使用Microsoft商标或徽标不得引起混乱或暗示Microsoft赞助。任何使用第三方商标或徽标都遵守这些第三方政策。