不确定的参数化是在C ++和Java中构建应用程序体系结构的通用方法。 价格

C ++是一种令人困惑的语言,其主要缺点是难以创建孤立的代码块。 在一个典型的项目中,这一切都取决于一切。 本文说明如何编写高度隔离的代码,这些代码最少依赖于特定的库(包括标准库)和实现,从而减少任何代码对一组接口的依赖性。 另外,将提出用于代码参数化的体系结构解决方案,这可能不仅使C ++程序员感兴趣,而且可能使Java程序员感兴趣。 重要的是,就开发时间而言,提出的解决方案非常经济。

免责声明 :在本文中,我收集了有关理想架构的想法。 有些想法不是我的想法(但我不记得是谁的想法),有些想法很普遍并且为大家所熟知-这并不重要,因为我没有提供有关好的架构的想法,而是一些特定的代码,这些代码可使该架构以最低的价格实现。

免责声明N2 :我会对用语言表达的建设性反馈感到满意。 如果您理解得比我差,并责骂我,则意味着我对某处的解释不够清楚,因此可以对文本进行重新处理。 如果您比我了解得更多,则意味着我将获得宝贵的经验。 预先感谢。

免责声明N3 :我从头开始编写了大型应用程序,但没有编写服务器和客户端企业应用程序。 那里的一切都不同,也许我的经验对于该领域的专家来说似乎很奇怪。 而且,本文的内容并非如此,这里根本不考虑相同的可伸缩性问题。
免责声明N4 (根据评论更新):一些评论者建议我重新发明Fowler,并提供众所周知的设计模式。 绝对不是这样。 我提出了一个非常小的参数化工具,该工具可让您以最少的涂抹来实现这些模式。 不仅包括Fowler的依赖注入和服务定位器,而且还包括-使用TypedSet类,还可以经济地实施一系列策略。 在这种情况下,Fowler通过行进行访问,这很昂贵-我的零成本工具为零成本(如果绝对严格的话,则用log(N)代替2M * log(N),其中M是服务定位器参数字符串的长度。在c ++ 20中出现constexpr typeid之后,价格应完全变为零)。 因此,请您不要将本文的含义扩展到设计模式。 在这里,您将只找到一种廉价实现这些模式的方法。

这些示例将在C ++中进行,但是以上所有内容在Java中都是可以实现的。 也许,随着时间的流逝,如果您的请求中包含了对Java的要求,我会给出Java的工作代码。

第1部分。真空中的球形结构


在彻底解决所有困难之前,您需要正确创建它们。 在适当的地方熟练地为自己制造困难,可以极大地促进他们的解决。 为此,我们为解决方案制定了一个目标,我们将提出方法-良好体系结构的最低原则。

实际上,好的体系结构的魔力只是两个原理,下面写的只是一个解码。 第一个原则是代码可测试性。 可测试性就像Ariadne的线程一样,可以带您进入良好的体系结构。 如果您不知道如何编写功能测试,那么您将破坏体系结构。 如果您不知道如何创建一个好的架构,请考虑对计划的功能进行的测试-您会自动为自己创建一个很高的架构质量标准。 关于测试的想法会自动增加模块化,降低连接性,并使体系结构更具逻辑性。

我并不是说TDD。 许多程序员的典型病态是对某处阅读的技术的宗教崇拜,却不了解其有效性的局限性。 当一些程序员正在编写代码,有一个测试部门并且主管部门了解为什么需要良好的编码实践时,TDD很好,它不仅愿意为解决问题的某些代码付出代价,而且还愿意为它的可靠性付出代价。 如果您的上级不准备付款,您将不得不更加经济地工作。 尽管如此,您仍然必须测试代码-当然,除非您有自我保护的感觉。

第二个原则是模块化。 更准确地说,高度绝缘的模块化,而无需使用与模块本身无关的库/硬代码。 现在,在设计服务器体系结构时,将单一组件划分为微服务已经成为一种时尚。 我将告诉您一个可怕的秘密-整体中的每个模块都应该像微服务。 从某种意义上说,它应该在测试环境中以最少的连接标头轻松地从通用代码中脱颖而出。 尚不清楚,但我将举一个例子进行说明:您是否曾经尝试从boost中分配shared_ptr? 如果同时您不仅设法拖累了全部提振,而且仅拖拽了其一半原材料,那么这意味着您杀死了三到五天,以砍掉不必要的瘾! 同时,您拖延了shared_ptr绝对无关的事实!!!

比犯错误更糟-这是建筑犯罪。

有了良好的体系结构,您应该能够轻松地删除shared_ptr,并用测试版本快速替换与shared_ptr不相关的所有内容。 例如,分配器的测试版本。 或忘记提振。 假设您编写了一个xml / html解析器。 您需要使用字符串和解析器的文件。 而且,如果我们谈论的是不依赖于特定生产/软件公司需求的理想架构,那么对于具有理想架构的解析器,我们无权使用std :: istream,std :: file_system,std :: string和带有字符串的硬编码搜索操作在解析器中。 我们必须提供一个流接口,一个用于文件操作的接口(可能分为子接口,但是仍然必须通过文件操作模块的接口来访问子接口),一个用于处理字符串的接口,一个分配器接口,以及理想地还用于该行本身的接口。 结果,我们可以轻松地将与解析无关的所有内容替换为测试空白,或插入分配器的测试版本/使用文件/字符串搜索并进行其他检查。 解决方案的多功能性将提高-明天,在流接口下将没有文件,但在Internet上的某个站点上,没有人会注意到它。 您可以用Qt替换标准库,然后切换到Visual C ++,然后仅使用Linux东西-所做的更改将很小。 作为破坏者,我要说的是,采用这种方法时,价格问题就出现了全面增长的情况-用接口覆盖所有内容,包括标准库的元素,都是很昂贵的,但这不是目标,而是解决方案。

通常,本文所宣称的基本模块即微服务原理是C ++的一个痛点,并且通常是典型的加号代码。 如果您创建声明文件和与实现分开的接口,则仍然可以创建cpp文件彼此之间的独立性/隔离性,这是相对的,而不是100%的隔离,然后通常将标头编织成一个整体,没有它们,如果没有肉就不能撕下任何东西。 尽管这对编译时间有可怕的影响,但确实如此。 此外,即使实现了标题的独立性,这也自动意味着无法聚合类。 实际上,在C ++中实现.cpp文件和标头的独立性的唯一方法是声明以前使用的类(不定义它们),然后仅使用指向它们的指针。 一旦使用类本身而不是头文件中的类指针(即对其进行聚合),您将创建一堆包括该标题的所有.cpp-shnik和包含类定义的.cpp-shnik。 仍然有fastpimpl,但是可以保证在cpp级别上创建依赖项。

因此,对于一个好的体系结构,模块的隔离很重要-能够通过第一个标题连接宏和主要类型的库,一个用于声明的第二标题以及连接一组接口的多个包含物来拉出一个模块。 而且只有与此功能相关的内容,其他所有内容都应存储在其他模块中,并且只能通过接口进行访问。

我们逐一陈述了良好架构的主要特征,包括上面指出的要点。

让我们定义术语“模块”。 模块是逻辑相关功能的总和。 例如,使用流或文件工作,或html解析器。

“文件工作”模块可以结合许多功能-打开文件,关闭,定位,读取属性,读取文件大小。 同时,文件夹扫描器可以设计为“文件工作”界面的一部分,也可以设计为单独的模块,并且可以将使用流的工作放到单独的模块中。 但是,这不会干扰通过“文件工作”来组织对所有其他模块对流和文件夹扫描程序的间接访问。 这不是必需的,但很合逻辑。

  1. 模块化。 势在必行的“微服务模块”。
  2. 80%的时间将20%的代码分配给一个单独的库-该程序的核心
  3. 每个模块的每个功能的可测试性
  4. 界面,因为缺少硬编码。 您只能调用与模块功能直接相关的硬代码,并且您必须对其他模块进行其他直接库调用,并通过界面进行访问。
  5. 通过接口与外部环境完全隔离模块。 禁止“钉牢”与课程功能无关的实施。 更根本的是,使用接口/适配器/装饰器隔离库(包括标准库)
  6. 仅在对性能至关重要的情况下,才使用聚集类或创建类变量或fastpimpl的方法。

当然,我们将想出如何以较低的价格快速实现所有这些功能,但是我想提请您注意另一个问题,对于我们来说,解决该问题将是一个好处-依赖于平台的参数的传输。 例如,如果您需要制作在Android和Windows上均能正常工作的代码,则将与平台相关的算法分配到单独的模块中是合乎逻辑的。 在这种情况下,可能是android的实现可能需要引用Java(jni)环境,JNIEnv *,并可能引用几个Java对象。 Windows上的实现可能需要程序的工作文件夹(在android上,可以从系统中请求,带有JNIEnv *)。 诀窍是在Windows上下文中不存在相同的JNIEnv *,因此即使类型化联合或其c ++替代std :: variant也是不可能的。 当然,您可以使用void *向量或std ::任何向量作为参数,但是说实话,这是一个非典型的拐杖。 非典型的-因为它拒绝了c ++的主要优点,即强类型化。 这比非典更为危险。

此外,我们将分析如何以严格的典型方式解决此问题。

第2部分。魔术子弹及其价格标签


因此,假设我们有大量的代码需要从头开始编写,结果将是一个非常大的项目。

如何按照我们确定的原则进行组装?

所有手册都认可的经典方法是将所有内容分为界面和策略。 在接口和策略的帮助下,如果有很多接口和策略,我们项目的任何子问题都可以隔离到一定程度,以至于“微服务模块”原则将开始起作用。 但是我的个人经验是,如果将项目分为20-30个部分,将其隔离到“微模块模块”的级别,那么您将成功。 但是,良好体系结构的主要特征是能够在项目上下文之外测试任何类。 而且,如果您已经隔离了每个班级,那么已经有500多个模块,以我的经验,这将使开发时间增加3-5倍,这意味着在“战斗条件”下您将不会这样做,并且会在价格和质量之间折衷。

有人可能会对此表示怀疑,这将是他自己的权利。 让我们做一个粗略的估计。 让中产阶级拥有3-5个成员,20个函数和3个构造函数。 加上6-10个getter和setter(mutators)以访问我们的成员。 总共大约40个单元。 在一个典型的项目中,每个“中心”类平均需要访问五个功能,而不能访问3个中心。例如,很多类需要分配器,文件系统,字符串,流和数据库访问权限。

每个策略/接口都需要一个类型为std::shared_ptr<CreateStreamStrategy> m_create_stream; 。 两个mutator,以及三个构造函数中的每一个的初始化。 加上我们类初始化的某处,您将需要myclass->SetCreateStreamStrategy( my_create_stream_strategy )调用诸如myclass->SetCreateStreamStrategy( my_create_stream_strategy )类的东西,每个接口/策略总共需要8个单元,并且由于我们有大约5个单元,因此将有40个单元。 也就是说,我们使源类繁琐了两倍。 尽管事实似乎并没有本质上的改变,但简单性的丧失将不可避免地影响可读性,以及调试过程中其他时间的一半。

问题是。 怎么做,但价格最低? 首先想到的是模板的静态参数化,采用Alexandrescu和Loki库的样式。

我们正在写风格一流的课

 template < struct Traits > class MyClass { public: void DoMainTaskFunction() { ... MyStream stream = Traits::streamwork::Open( stream_name ); ... } }; 

该决定具有我们在第一部分中确定的所有架构优势。 但是也有很多缺点。

我本人很乐意为自己做些准备,但我为自己感到遗憾:普通代码中的模板仅被模板魔术师所喜爱。 大量使用“模板”一词的程序员对此有些皱眉。 而且,在行业中,绝大多数加号实际上不是加号,而是在不了解加号的精明的C ++系统中经过再培训的,但是他们属于“模板”一词并假装死了。

如果我们将其翻译成生产语言,则维护静态参数化上的代码将更加昂贵且更加复杂。

同时,如果出于更大的可读性的目的,想要在类之外小心地删除函数的主体,那么我们就会对模板的名称和模板的参数有很多of草。 并且在发生编译错误的情况下,我们可以使用一堆复杂的嵌套模板来获得人类可读的原因和问题区域的长货架。

但是,有一个简单的出路。 作为模板魔术师,我声明几乎所有可以使用静态参数化/静态多态性完成的操作都可以转换为动态多态性。 不,当然,我们不会彻底消除模板的弊端-但我们不会用慷慨的手将其散布在每个类中进行参数化,而是将其限制在几个工具类中。

第三部分。 建议的解决方案以及为此解决方案编码的代码


所以那里! 符合模板类TypedSet。 他将一个这种类型的智能指针与一个单一类型相关联。 而且,对于指定的类型,它可能有一个对象,但可能没有。 我不喜欢这个名字-如果在评论中告诉我一个更成功的选择,我将不胜感激。

一种类型-一种对象。 但是类型的数量没有限制! 因此,您可以传递此类作为参数化器。

我想提请您注意一点。 似乎有时某个接口下可能需要两个对象。 实际上,如果出现这种需求,那么(我认为)这意味着架构错误。 也就是说,如果您在一个接口下有两个对象,那么它们将不再是功能访问接口:这些要么是函数的输入变量,要么您没有一个但需要访问两个功能,那么最好将接口分为两个。

我们将实现三个基本功能:创建,获取和具有。 因此,创建,接收和验证元素的存在。

 /// @brief    .      ,    ///           /// class TypedSet { public: template <class TypedElement> void Create( const std::shared_ptr<TypedElement> & value ); template <class TypedElement> std::shared_ptr<TypedElement> Get() const; template <class TypedElement> bool Has() const; size_t GetSize() const { return storage_.size(); } protected: typedef std::map< size_t, std::shared_ptr<void> > Storage; Storage const & storage() const { return storage_; } Storage & get_storage() { return storage_; } private: Storage storage_; }; template <class TypedElement> void TypedSet::Create( const std::shared_ptr<TypedElement> & value ) { size_t hash = typeid(TypedElement).hash_code(); if ( storage().count( hash ) > 0 ) { LogError( "Access Violation" ); return; } std::shared_ptr<void> to_add ( value ); get_storage().insert( std::pair( typeid(TypedElement).hash_code(), to_add ) ); } template <class TypedElement> bool TypedSet::Has() const { size_t hash = typeid(TypedElement).hash_code(); return storage().count( hash ) > 0; } template <class TypedElement> std::shared_ptr<TypedElement> TypedSet::Get() const { size_t hash = typeid(TypedElement).hash_code(); if ( storage().count( hash ) > 0 ) { std::shared_ptr<void> ret( storage().at(hash) ); return std::static_pointer_cast<TypedElement>( ret ); } else { LogError( "Access Violation" ); return std::shared_ptr<TypedElement> (); } } 

顺便说一句,我看到了同事用Qt编写的替代解决方案。 在那里,通过单例访问所需界面,该单例通过文本行(!!!)将“所需”界面“映射”到打包到Varaint中,并且在投射此选项之后,可以使用结果。

 GlobalConfigurator()["FileSystem"].Get().As<FileSystem>() 

当然可以,但是计算长度和进一步哈希字符串的开销对于我乐观的灵魂来说有点吓人。 在这里,开销为零,因为 所需接口的选择在编译时执行。

基于TypedSet,我们可以制作StrategiesSet类,该类已经更加高级。在其中,我们不仅将为每个功能的每个访问接口存储一个对象,而且还将为每个接口(以下称为策略)存储一个带有此策略参数的附加TypedSet。我澄清一下:与函数变量不同,参数是在程序初始化期间设置一次或在大型程序运行时设置一次。参数使您可以使代码真正跨平台。正是在它们中,我们驱动了整个与平台相关的厨房。

在这里,我们将拥有更多基本功能:Create,Get,CreateParamsSet和GetParamsSet。尚未放置,因为它在体系结构上是多余的:如果您的代码引用了使用文件系统的功能,而调用代码未提供该代码,则只能抛出异常或断言,或使程序为sepukka 调用abort()函数。

 class StrategiesSet { public: template <class Strategy> void Create( const std::shared_ptr<Strategy> & value ); template <class Strategy> std::shared_ptr<Strategy> Get(); template <class Strategy> void CreateParamsSet(); template <class Strategy> std::shared_ptr<TypedSet> GetParamsSet(); template <class Strategy, class ParamType> void CreateParam( const std::shared_ptr<ParamType> & value ); template <class Strategy, class ParamType> std::shared_ptr<ParamType> GetParam(); protected: TypedSet const & strategies() const { return strategies_; } TypedSet & get_strategies() { return strategies_; } TypedSet const & params() const { return params_; } TypedSet & get_params() { return params_; } template <class Type> struct ParamHolder { ParamHolder( ) : param_ptr( std::make_shared<TypedSet>() ) {} std::shared_ptr<TypedSet> param_ptr; }; private: TypedSet strategies_; TypedSet params_; }; template <class Strategy> void StrategiesSet::Create( const std::shared_ptr<Strategy> & value ) { get_strategies().Create<Strategy>( value ); } template <class Strategy> std::shared_ptr<Strategy> StrategiesSet::Get() { return get_strategies().Get<Strategy>(); } template <class Strategy> void StrategiesSet::CreateParamsSet( ) { typedef ParamHolder<Strategy> Holder; std::shared_ptr< Holder > ptr = std::make_shared< Holder >( ); ptr->param_ptr = std::make_shared< TypedSet >(); get_params().Create< Holder >( ptr ); } template <class Strategy> std::shared_ptr<TypedSet> StrategiesSet::GetParamsSet() { typedef ParamHolder<Strategy> Holder; if ( get_params().Has< Holder >() ) { return get_params().Get< Holder >()->param_ptr; } else { LogError("StrategiesSet::GetParamsSet : get unexisting!!!"); return std::shared_ptr<TypedSet>(); } } template <class Strategy, class ParamType> void StrategiesSet::CreateParam( const std::shared_ptr<ParamType> & value ) { typedef ParamHolder<Strategy> Holder; if ( !params().Has<Holder>() ) CreateParamsSet<Strategy>(); if ( params().Has<Holder>() ) { std::shared_ptr<TypedSet> params_set = GetParamsSet<Strategy>(); params_set->Create<ParamType>( value ); } else { LogError( "Param creating error: Access Violation" ); } } template <class Strategy, class ParamType> std::shared_ptr<ParamType> StrategiesSet::GetParam() { typedef ParamHolder<Strategy> Holder; if ( params().Has<Holder>() ) { return GetParamsSet<Strategy>()->template Get<ParamType>(); //   template          .    . } else { LogError( "Access Violation" ); return std::shared_ptr<ParamType> (); } } 

另一个优点是,在原型开发阶段,您可以制作一个超大型的打字类,将所有模块塞入其中,然后将其作为参数传递给所有模块,然后迅速变小,然后将其安静地分成每个模块所需的最小片段。

好了,还有一个很小的(至今)过于简化的用例。希望您在评论中提出我想作为一个简单示例看到的内容,并且我将对本文进行小的升级。正如流行的编程智慧所说:“尽早发布,并在发布后使​​用反馈进行改进。”

 class Interface1 { public: virtual void Fun() { printf("\niface1\n");} virtual ~Interface1() {} }; class Interface2 { public: virtual void Fun() { printf("\niface2\n");} virtual ~Interface2() {} }; class Interface3 { public: virtual void Fun() { printf("\niface3\n");} virtual ~Interface3() {} }; class Implementation1 : public Interface1 { public: virtual void Fun() override { printf("\nimpl1\n");} }; class Implementation2 : public Interface2 { public: virtual void Fun() override { printf("\nimpl2\n");} }; class PrintParams { public: virtual ~PrintParams() {} virtual std::string GetOs() = 0; }; class PrintParamsUbuntu : public PrintParams { public: virtual std::string GetOs() override { return "Ubuntu"; } }; class PrintParamsWindows : public PrintParams { public: virtual std::string GetOs() override { return "Windows"; } }; class PrintStrategy { public: virtual ~PrintStrategy() {} virtual void operator() ( const TypedSet& params, const std::string & str ) = 0; }; class PrintWithOsStrategy : public PrintStrategy { public: virtual void operator()( const TypedSet& params, const std::string & str ) override { auto os = params.Get< PrintParams >()->GetOs(); printf(" Printing: %s (OS=%s)", str.c_str(), os.c_str() ); } }; void TestTypedSet() { using namespace std; TypedSet a; a.Create<Interface1>( make_shared<Implementation1>() ); a.Create<Interface2>( make_shared<Implementation2>() ); a.Get<Interface1>()->Fun(); a.Get<Interface2>()->Fun(); Log("Double creation:"); a.Create<Interface1>( make_shared<Implementation1>() ); Log("Get unexisting:"); a.Get<Interface3>(); } void TestStrategiesSet() { using namespace std; StrategiesSet printing; printing.Create< PrintStrategy >( make_shared<PrintWithOsStrategy>() ); printing.CreateParam< PrintStrategy, PrintParams >( make_shared<PrintParamsWindows>() ); auto print_strategy_ptr = printing.Get< PrintStrategy >(); auto & print_strategy = *print_strategy_ptr; auto & print_params = *printing.GetParamsSet< PrintStrategy >(); print_strategy( print_params, "Done!" ); } int main() { TestTypedSet(); TestStrategiesSet(); return 0; } 

总结


因此,我们解决了一个重要的问题:我们只在类中保留了与类功能直接相关的接口。其余的“推入”到StrategiesSet中,同时避免了用不必要的元素使类混乱,并避免“钉住”算法所需的某些功能。这将使我们不仅可以编写高度隔离的代码,而且对实现和库的依赖为零,而且还可以节省大量时间。

示例和工具类的代码可在此处 Upd找到

。从11/13/2019
实际上,此处显示的代码只是可读性的简化示例。事实是typeid()。Hash_code是在现代编译器中缓慢而低效地实现的。它的使用扼杀了许多含义。此外,正如受人尊敬的0xd34df00d所建议的那样,该标准不能保证通过哈希码区分类型的能力(但实际上,此方法有效)。但是这个例子很好读。我改写了没有typeid()的TypedSet,而且Hash_code()用数组替换了map(但是可以通过更改#if中的一位来快速从map切换到array,反之亦然)。事实证明这比较困难,但实际使用起来更有趣。
在coliru
 namespace metatype { struct Counter { size_t GetAndIncrease() { return counter_++; } private: size_t static inline counter_ = 1; }; template <typename Type> struct HashGetterBody { HashGetterBody() : hash_( counter_.GetAndIncrease() ) { } size_t GetHash() { return hash_; } private: Counter counter_; size_t hash_; }; template <typename Type> struct HashGetter { size_t GetHash() {return hasher_.GetHash(); } private: static inline HashGetterBody<Type> hasher_; }; } // namespace metatype template <typename Type> size_t GetTypeHash() { return metatype::HashGetter<Type>().GetHash(); } namespace details { #if 1 //   ,        () class TypedSetStorage { public: static inline const constexpr size_t kMaxTypes = 100; typedef std::array< std::shared_ptr<void>, kMaxTypes > Storage; void Set( size_t hash_index, const std::shared_ptr<void> & value ) { ++size_; assert( hash_index < kMaxTypes ); // too many types data_[hash_index] = value; } std::shared_ptr<void> & Get( size_t hash_index ) { assert( hash_index < kMaxTypes ); return data_[hash_index]; } const std::shared_ptr<void> & Get( size_t hash_index ) const { if ( hash_index >= kMaxTypes ) return empty_ptr_; return data_[hash_index]; } bool Has( size_t hash_index ) const { if ( hash_index >= kMaxTypes ) return 0; return (bool)data_[hash_index]; } size_t GetSize() const { return size_; } private: Storage data_; size_t size_ = 0; static const inline std::shared_ptr<void> empty_ptr_; }; #else //    ,        (std::map) class TypedSetStorage { public: typedef std::map< size_t, std::shared_ptr<void> > Storage; void Set( size_t hash_index, const std::shared_ptr<void> & value ) { data_[hash_index] = value; } std::shared_ptr<void> & Get( size_t hash_index ) { return data_[hash_index]; } const std::shared_ptr<void> & Get( size_t hash_index ) const { return data_.at(hash_index); } bool Has( size_t hash_index ) const { return data_.count(hash_index) > 0; } size_t GetSize() const { return data_.size(); } private: Storage data_; }; #endif } // namespace details /// @brief    .      ,    ///           /// class TypedSet { public: template <class TypedElement> void Create( const std::shared_ptr<TypedElement> & value ); template <class TypedElement> std::shared_ptr<TypedElement> Get() const; template <class TypedElement> bool Has() const; size_t GetSize() const { return storage_.GetSize(); } protected: typedef details::TypedSetStorage Storage; Storage const & storage() const { return storage_; } Storage & get_storage() { return storage_; } private: Storage storage_; }; template <class TypedElement> void TypedSet::Create( const std::shared_ptr<TypedElement> & value ) { size_t hash = GetTypeHash<TypedElement>(); if ( storage().Has( hash ) ) { LogError( "Access Violation" ); return; } std::shared_ptr<void> to_add ( value ); get_storage().Set( hash, to_add ); } template <class TypedElement> bool TypedSet::Has() const { size_t hash = GetTypeHash<TypedElement>(); return storage().Has( hash ); } template <class TypedElement> std::shared_ptr<TypedElement> TypedSet::Get() const { size_t hash = GetTypeHash<TypedElement>(); if ( storage().Has( hash ) ) { std::shared_ptr<void> ret( storage().Get( hash ) ); return std::static_pointer_cast<TypedElement>( ret ); } else { LogError( "Access Violation" ); return std::shared_ptr<TypedElement> (); } } 

这里的访问是线性时间进行的,在启动main()之前对类型哈希进行计数,损失仅用于验证检查,如果需要,可以将其丢弃。

Source: https://habr.com/ru/post/zh-CN475268/


All Articles