工信部网站备案的需要幕布wordpress下载付费

张小明 2025/12/19 16:36:01
工信部网站备案的需要幕布,wordpress下载付费,网站怎么做才能被百度抓取到,有关建设网站的英语文献信息隐藏#xff08;Information Hiding#xff09;比看起来难得多——理解 信息隐藏是软件工程的核心原则之一#xff0c;但在实际开发中很难落实。指出软件工程教育标准普遍低于其他工程学科#xff0c;因此开发者常常难以正确应用信息隐藏。此外#xff0c;还有一些常见…信息隐藏Information Hiding比看起来难得多——理解信息隐藏是软件工程的核心原则之一但在实际开发中很难落实。指出软件工程教育标准普遍低于其他工程学科因此开发者常常难以正确应用信息隐藏。此外还有一些常见原因导致信息隐藏失败1⃣ 流程图本能The Flowchart Instinct很多程序员习惯从流程图出发设计程序直接把控制流写死。问题这种方法把实现细节暴露给外部违反信息隐藏原则。理解公式化程序结构≈控制流主导⇒模块内部实现暴露 \text{程序结构} \approx \text{控制流主导} \Rightarrow \text{模块内部实现暴露}程序结构≈控制流主导⇒模块内部实现暴露2⃣ 为变化做计划似乎是“过度计划”Planning for Change Seems like too much Planning开发者倾向于只做眼前需求而忽略为未来变化设计抽象层。信息隐藏要求对可能变化的部分做封装但这在短期项目中往往被忽略。逻辑关系不为变化设计⇒系统脆弱⇒修改困难 \text{不为变化设计} \Rightarrow \text{系统脆弱} \Rightarrow \text{修改困难}不为变化设计⇒系统脆弱⇒修改困难3⃣ 坏模型Bad Models如果系统模型设计不合理例如对象、模块关系混乱信息隐藏无法实现。坏模型会导致模块耦合度高内部实现暴露可以表示为坏模型⇒高耦合⇒信息暴露 \text{坏模型} \Rightarrow \text{高耦合} \Rightarrow \text{信息暴露}坏模型⇒高耦合⇒信息暴露4⃣ 扩展坏的软件Extending Bad Software当基础软件设计不合理时后续的功能扩展会增加复杂性使得信息隐藏更难。核心问题累积的设计缺陷导致模块边界混乱。表示坏软件基础扩展⇒信息隐藏困难 \text{坏软件基础} \text{扩展} \Rightarrow \text{信息隐藏困难}坏软件基础扩展⇒信息隐藏困难5⃣ 假设常常不被注意Assumptions often go unnoticed开发者对系统的隐含假设往往未显式表达。例子作者的 KwIC Index 中存在重大缺陷就是因为一些假设未被发现。表示隐含假设∉文档/接口⇒隐藏信息被破坏 \text{隐含假设} \not\in \text{文档/接口} \Rightarrow \text{隐藏信息被破坏}隐含假设∈文档/接口⇒隐藏信息被破坏6⃣ 将系统环境反映在软件结构中Reflecting the system environment in the software structure一些程序设计直接依赖操作系统、硬件等环境而非抽象接口。问题模块内部实现暴露使信息隐藏原则失效。表示环境依赖⇒实现暴露⇒信息隐藏失败 \text{环境依赖} \Rightarrow \text{实现暴露} \Rightarrow \text{信息隐藏失败}环境依赖⇒实现暴露⇒信息隐藏失败7⃣ “哦太糟了我们改了它”“Oh, too bad we changed it”当系统变化时如果设计没有良好封装会导致大面积修改。体现信息隐藏失败的直接后果。表示设计未隐藏变化点需求变化⇒系统破坏 \text{设计未隐藏变化点} \text{需求变化} \Rightarrow \text{系统破坏}设计未隐藏变化点需求变化⇒系统破坏总结信息隐藏失败的原因大致可以归纳为设计思想问题流程图本能、坏模型、坏软件扩展对变化缺乏前瞻过少规划、未隐藏变化点假设未明确、过度依赖环境核心思想信息隐藏对变化点封装抽象设计假设显式化 \text{信息隐藏} \text{对变化点封装} \text{抽象设计} \text{假设显式化}信息隐藏对变化点封装抽象设计假设显式化实现细节隐藏Hiding Implementation Details并不简单——理解隐藏实现细节是软件设计的重要原则但实际中很难完全做到。原因大致分为四类1⃣ 因为我们懒惰或者认为不重要Lazy or Not Important很多开发者在时间紧迫时会舍弃“优雅设计”直接使用暴露的实现细节。心态可能包括时间压力没有时间做抽象设计或封装。认为不重要觉得将来可以轻松修改现实往往不可能或很困难。例子std::pairFirst,Secondp;autokp.first;autovp.second;尽管存在其他安全的访问方法但直接访问first和second仍然暴露了私有字段。核心思想懒惰 时间压力⇒直接暴露实现细节 \text{懒惰 时间压力} \Rightarrow \text{直接暴露实现细节}懒惰时间压力⇒直接暴露实现细节演讲目的劝告开发者不要懒惰即使看似不重要也要重视信息隐藏。2⃣ 因为我们没有意识到自己暴露了私有细节Unaware of Exposing Details开发者往往在提供接口时无意中泄露了私有信息。例子classFoo{std::mapstd::string,intproperties;public:conststd::mapstd::string,intgetProperties()const;};暴露的私有细节返回的std::map引用允许外部直接访问和修改内部数据。逻辑关系返回内部引用⇒外部可修改私有数据⇒信息隐藏失败 \text{返回内部引用} \Rightarrow \text{外部可修改私有数据} \Rightarrow \text{信息隐藏失败}返回内部引用⇒外部可修改私有数据⇒信息隐藏失败3⃣ 技术约束Technical Constraints有些语言或编译器限制使得理想的封装方式难以实现。例子classFoo{Foo(intvalue):value(value){}// 想让构造函数私有但编译不通过public:staticstd::unique_ptrFoocreate(intvalue){returnstd::make_uniqueFoo(value);}};autofooFoo::create(7);解决办法技术约束通常可以找到替代方案如工厂函数、智能指针等。核心思想技术限制≠无法隐藏⇒有解决方案可用 \text{技术限制} \neq \text{无法隐藏} \Rightarrow \text{有解决方案可用}技术限制无法隐藏⇒有解决方案可用4⃣ 权衡折中Tradeoffs有时为了性能或便利需要暴露部分实现细节避免虚函数调用开销。提供随机访问接口以提升性能。需要进行权衡某些场景下妥协是合理的。也可能存在两全其美的方案。逻辑表示暴露数据⇔性能提升但需权衡设计原则 \text{暴露数据} \Leftrightarrow \text{性能提升} \quad \text{但需权衡设计原则}暴露数据⇔性能提升但需权衡设计原则总结公式化隐藏实现细节失败的原因可以表示为信息隐藏失败懒惰/不重视无意识暴露技术约束设计折中 \text{信息隐藏失败} \text{懒惰/不重视} \text{无意识暴露} \text{技术约束} \text{设计折中}信息隐藏失败懒惰/不重视无意识暴露技术约束设计折中懒惰/不重视→ 时间压力、认为不重要无意识暴露→ 返回内部引用、直接访问内部字段技术约束→ 语言/编译器限制但可解决设计折中→ 性能或便利导致暴露目标Our Goal在这个例子中我们想要分析代码示例观察如何暴露实现细节implementation details。理解为什么这是不好的设计每个例子中设计问题是什么。提出改进方案隐藏实现细节同时保留所需功能。std::pair 示例问题未隐藏私有成员是不正确的设计观察std::pair.first std::pair.secondstd::pair的first和second是 public 的这是 C 标准库中的设计“意外”language accident。为什么是问题暴露数据成员无法控制行为。比如如果希望创建一个“懒计算”的second如它是first的平方直接访问 public 成员会破坏这种能力。信息隐藏原则成员应为 private⇒通过 getter/setter 或函数访问 \text{成员应为 private} \Rightarrow \text{通过 getter/setter 或函数访问}成员应为private⇒通过getter/setter或函数访问例子分析// 假设函数依赖于 PAIR 有 first 和 second 字段templatetypenamePAIRstd::ostreamoperator(std::ostreamout,constPAIRpair){returnoutpair.first pair.second;}// 使用 std::pairautostdpairstd::make_pair(bye,42);std::coutstdpairstd::endl;// 如果希望 second 惰性计算autosquarepairSquarePair(7);std::coutsquarepairstd::endl;问题点operator 模板依赖 public 字段这意味着只要PAIR有first和second无论它是 std::pair 还是自定义类型都可以被输出。但如果我们想让second是惰性计算的如first*first就必须隐藏内部实现否则会被外部直接访问。扩展能力受限直接暴露字段会破坏对未来行为的控制。无法插入逻辑如延迟求值、缓存结果、通知等。改进设计方向将数据成员设为 privateclassSquarePair{private:intfirst_;intsecond_;// 可以懒计算public:SquarePair(intx):first_(x),second_(0){}intfirst()const{returnfirst_;}intsecond()const{if(second_0)second_first_*first_;returnsecond_;}friendstd::ostreamoperator(std::ostreamout,constSquarePairp){returnoutp.first() p.second();}};好处外部无法直接访问second_保证惰性计算逻辑不被破坏。保持接口一致性通过函数访问而不是直接访问成员。总结公式化问题公式化公开成员⇒无法控制行为⇒设计不灵活 \text{公开成员} \Rightarrow \text{无法控制行为} \Rightarrow \text{设计不灵活}公开成员⇒无法控制行为⇒设计不灵活改进公式化成员 privategetter/setter 或友元函数⇒实现细节隐藏∧可扩展性 \text{成员 private} \text{getter/setter 或友元函数} \Rightarrow \text{实现细节隐藏} \wedge \text{可扩展性}成员privategetter/setter或友元函数⇒实现细节隐藏∧可扩展性结构体与类设计的一般问题structs, in general1. 基本 struct 示例structOrder{Price price;Quantity quantity;};问题struct默认是public所有成员都公开。当需求变化时比如支持不同类型的订单直接访问成员会导致修改困难。难以扩展无法在不破坏已有接口的情况下增加逻辑如惰性计算、验证、转换。2. 支持泛型订单Supporting Generic OrdersclassOrder{Price price;Quantity quantity;public:// 构造函数略PricegetPrice()const{returnprice;}QuantitygetQuantity()const{returnquantity;}};改进点将成员私有化private。提供 getter 函数访问。好处可以在 getter 中加入逻辑如验证、缓存、转换。支持“静态多态Static Polymorphism”或“鸭子类型Duck Typing”允许不同类实现同样接口。3. 更复杂的订单类型classTimedOrder{std::vectorstd::pairchrono::time_point,Priceprice_limits;Quantity quantity;public:PricegetPrice()const{/* 获取合适价格 */}QuantitygetQuantity()const{returnquantity;}};说明支持价格随时间变化的订单。依然可以通过接口getter与普通Order兼容。4. const 成员的问题Just a const memberclassPerson{std::string name;public:constId id;Person(std::string name,Id id):name(std::move(name)),id(id){}};问题const成员一旦初始化就不能修改。需求可能会变化例如需要支持多种身份证明SSN、护照等。可能需要更新护照信息同时保留旧护照号。结论直接用const绑定会限制未来扩展。正确的做法是将成员私有化并提供可控的访问与修改接口。5. 最终改进设计classPerson{std::string name;Id id;// 不再是 constpublic:Person(std::string name,Id id):name(std::move(name)),id(id){}// 提供接口操作 IdvoidnewPassport(PassportDetails pd){id.update(pd);}constIdgetId()const{returnid;}};好处支持未来需求变化。保证实现细节隐藏同时提供必要接口。提供封装和灵活性。6. 总结与经验Takeaway始终将数据成员包括 static 成员设为 private。提供受控访问接口Getter / Setter特定功能函数如newPassport()避免使用const限制未来需求扩展。接口设计优先考虑可扩展性和信息隐藏。原则公式化信息隐藏成员 private∧接口访问⇒可控扩展∧维护性强 \text{成员 private} \wedge \text{接口访问} \Rightarrow \text{可控扩展} \wedge \text{维护性强}成员private∧接口访问⇒可控扩展∧维护性强错误做法成员 public 或 const⇒难以扩展∧实现细节泄露 \text{成员 public 或 const} \Rightarrow \text{难以扩展} \wedge \text{实现细节泄露}成员public或const⇒难以扩展∧实现细节泄露1. API 使用与风险Hyrum 定律“开发者会依赖接口的所有可观察特性和行为即使这些行为没有在合同中声明。”含义当一个 API 有足够多的用户时不论你承诺什么所有可观察的行为都会被依赖。换句话说API 的设计细节一旦暴露出来就可能被外部代码绑定难以修改。研究发现应用通常只使用 API 的一小部分。未使用的 API 更容易出错。API 选项过多比如重载函数太多容易被误用。选项少不仅减少出错机会也减少测试工作量。2. API 设计原则Lean Mean保持 API 简洁只添加真正需要的功能。限制 API 的使用范围只暴露必要的参数和返回值。不泄露实现细节。根据使用场景暴露不同视图不同上下文可能只需要部分数据。通过视图或包装类Wrapper暴露相关数据。3. 上下文特定性Context-specific核心思想在某些使用场景下需要将成员或方法设为public。在其他使用场景下完全没有必要暴露。原则如果用户不需要就不要提供。如果提供了他们会使用。如果使用了他们可能会滥用。不要轻易相信外部使用者。结论仅仅把成员放在private并不够。API 设计必须考虑不同使用上下文提供最窄的接口。4. 如何传递参数只暴露必要信息方法值语义Value semantics直接传值需要时可封装在类中。接口Interface提供有限访问方法。Pimpl Idiom将实现隐藏在指针后面。包装类Wrapper/Views提供只读或部分访问。C20 Concepts用类型约束提供只暴露所需的接口。示例练习Const Map, Mutable Vals初始化一个 map。传递方式允许修改 map 的值但 map 本身不能修改如不能添加或删除 key。可用包装类或概念来实现。5. 核心 takeawayAPI 要尽量窄只暴露必要信息。上下文相关性决定什么可以暴露。避免未使用的 API 或冗余接口。技术手段可以帮助隐藏实现细节Pimpl只读视图类型约束信任无外人只暴露必需部分。公式化理解上下文敏感暴露API exposedf(context)where minimal exposure⇒less abuse, less bugs \text{API exposed} f(\text{context}) \quad \text{where minimal exposure} \Rightarrow \text{less abuse, less bugs}API exposedf(context)where minimal exposure⇒less abuse, less bugsHyrum 定律的启示任何可观察行为⇒可能被依赖⇒修改风险 \text{任何可观察行为} \Rightarrow \text{可能被依赖} \Rightarrow \text{修改风险}任何可观察行为⇒可能被依赖⇒修改风险1. 什么是 Concepts概念Concept对模板参数的约束constraint。在编译期求值为布尔值boolean。可用于函数重载解析overload resolution。优点Concepts 是 SFINAESubstitution Failure Is Not An Error的更优雅、可读性更高的替代方案。通过 Concepts可以清晰表达“哪些类型允许被模板接受”。数学理解Concept(T)true/false at compile-time \text{Concept}(T) \text{true/false at compile-time}Concept(T)true/false at compile-time表示模板参数是否满足约束。2. 例子函数模板重载目标实现两个print函数对可迭代类型如std::vector、std::arraytemplatetypenameIterablevoidprint(constIterableiterable){for(constautov:iterable)std::coutvstd::endl;}对其他类型templatetypenameTvoidprint(constTt){std::couttstd::endl;}问题上述代码在模板重载解析时可能出错因为编译器无法区分是否可迭代。需要 SFINAE 或 Concepts 来约束模板参数。3. SFINAE 方案templatetypenameIterable,std::enable_if_tis_iterable_vIterable*nullptrvoidprint(constIterableiterable){...}templatetypenameT,std::enable_if_t!is_iterable_vT*nullptrvoidprint(constTt){...}问题写法冗长不直观。is_iterable_v不是语言内置需要自定义。4. Concepts 方案方法 1requires约束templatetypenameTrequiresstd::ranges::rangeTvoidprint(constTiterable){for(constautov:iterable)std::coutvstd::endl;}templatetypenameTvoidprint(constTt){std::couttstd::endl;}说明requires表达了模板参数必须满足std::ranges::rangeT概念。简洁明了比 SFINAE 可读性高。方法 2约束模板类型templatestd::ranges::range Tvoidprint(constTiterable){...}将模板参数直接声明为满足某个概念的类型。方法 3约束函数参数abbreviated function templatevoidprint(conststd::ranges::rangeautoiterable){for(constautov:iterable)std::coutvstd::endl;}voidprint(constautot){std::couttstd::endl;}特点C20 允许在函数参数使用auto使函数成为模板函数。这种写法叫做“简化函数模板abbreviated function template”最简洁。5. 总结Concepts用于约束模板参数提供更清晰、可维护的重载逻辑。可以替代 SFINAE使模板代码可读性更高。对函数模板重载可通过requires子句约束模板类型。模板参数直接约束templateConcept T。函数参数约束auto 概念实现简化。使用场景比如打印函数、容器处理函数等需要区分可迭代类型和其他类型。1. 创建自定义 ConcepttemplatetypenameTconceptMeowablerequires(constT t){t.meow();// t 必须有一个 const 方法 meow};解释concept关键字用于定义概念Meowable。requires(const T t)表示对类型T的约束条件这里要求T类型的对象t必须有一个可调用的meow()方法并且该方法是const的。如果T满足这个条件则MeowableT为true否则为false。数学理解Meowable(T) ⟺ ∃t:T, t.meow() 是合法的且 const \text{Meowable}(T) \iff \exists t: T, \text{ t.meow() 是合法的且 const }Meowable(T)⟺∃t:T,t.meow()是合法的且const2. 定义类型满足 ConceptstructCat{voidmeow()const{std::coutmewo\n;}};structFrenchCat{voidmeow()const{std::coutmiaou\n;}};说明Cat和FrenchCat都有一个const方法meow()所以它们满足Meowable概念。如果某个类型没有meow()方法或者meow()不是const就不满足该概念。3. 使用 Concept 约束函数模板voiddo_meow(constMeowableautomeowable){meowable.meow();}解释const Meowable auto是简化函数模板abbreviated function template的写法。仅允许满足Meowable概念的类型作为参数传入。编译器在调用时会检查类型是否满足概念如果不满足则编译失败。例子调用Cat c;do_meow(c);// OK, Cat 满足 Meowabledo_meow(FrenchCat{});// OK, FrenchCat 满足 Meowable// do_meow(7); // 编译错误, int 不满足 Meowable4. 编译期静态断言static_assert(MeowableCat);static_assert(!Meowableint);static_assert(MeowableFrenchCat);作用static_assert在编译期检查类型是否满足概念。可以作为测试保证模板约束的正确性。如果断言失败编译器报错。5. 优势总结清晰表达意图Meowable直接描述了“可以喵”的类型而不需要手动写 SFINAE。编译期安全不符合概念的类型直接编译错误避免运行期错误。可组合性Concepts 可以组合如Meowable Serializable等。简化模板重载与auto配合使用使模板函数声明简洁voiddo_meow(constMeowableautomeowable);静态断言支持static_assert可以直接用概念检查类型是否符合约束。6. 扩展可以定义更复杂的概念例如要求方法返回特定类型templatetypenameTconceptMeowableIntrequires(constT t){{t.meow()}-std::same_asint;};这里要求t.meow()的返回类型必须是int。总结概念Concept是 C20 强大的模板约束工具。自定义概念可以约束类型成员函数、操作符等。简化函数模板Concepts能让模板代码既安全又可读。1.requires的两种用法在 C20 中requires主要有两种用途requires子句requires clause用于模板参数或函数声明指定约束条件。语法templatetypenameTrequiresAddableTvoidfoo(T t){...}含义模板参数T必须满足AddableT概念或布尔常量表达式否则编译失败。requires表达式requires expression用于定义自定义概念描述某种约束。语法templateclassTconceptFooablerequires(T t){t.foo();// T 必须有 foo() 方法};2. requires 子句约束条件类型requires子句必须是一个可以在编译期求值为bool的常量表达式包括constexpr booltemplatetypenameTvoidfoo(T t)requiresfalse{}// 永远不满足布尔常量表达式例如templatetypenameTvoidfoo(T t)requires(sizeof(T)4){}// 限制类型大小基于类型特性的布尔值#includetype_traitstemplatetypenameTvoidfoo(T t)requiresstd::is_integral_vT{}// 必须是整数类型已有概念#includeconceptstemplatetypenameTvoidfoo(T t)requiresstd::integralT{}// T 必须是整数概念类型复合约束逻辑与/或/非templatetypenameTvoidfoo(T t)requires(std::integralTsizeof(T)1){}// T 必须是整数且大小为 1 字节char 或 bool3. 示例分析示例 1类型为整数才允许调用templatetypenameTconstexprboolis_intfalse;templateconstexprboolis_intinttrue;templatetypenameTvoidfoo(T t)requiresis_intT{}intmain(){foo(8);// ok, Tint// foo(); // fails, Tconst char* 不满足 is_int}is_intT是一个布尔常量表达式。requires子句会在编译期判断T是否满足条件。不满足时编译器直接报错。示例 2限制类型大小templatetypenameTvoidfoo(T t)requires(sizeof(T)4){}intmain(){foo(8);// ok, sizeof(int)4// foo(); // fails, sizeof(const char*)8}requires子句可以使用常量表达式计算类型大小。避免运行时出错。示例 3组合约束#includeconceptstemplatetypenameTvoidfoo(T t)requires(std::integralTsizeof(T)1){}intmain(){foo(a);// ok, charfoo(false);// ok, bool// foo(8); // fails, int size1// foo(); // fails, const char* 不满足整数概念}复合约束可用逻辑与/ 或||组合多个条件。语义非常直观类型必须同时满足所有约束。4. 总结requires子句用于模板或函数声明限定类型必须满足条件。可以使用布尔常量表达式、类型特性或概念。requires表达式用于自定义概念约束类型必须有特定成员或操作符。优势编译期检查类型避免运行期错误。替代复杂的 SFINAE语法清晰。可组合和复用增强模板代码的可读性和安全性。数学形式假设C(T)为约束条件则T 可以使用函数 foo ⟺ C(T)true T \text{ 可以使用函数 foo } \iff C(T) \text{true}T可以使用函数foo⟺C(T)true示例C(T) (\text{std::integralT} \wedge \text{sizeof(T)}1)foo(T) 仅当 T∈char,bool foo(T) \text{ 仅当 } T \in { char, bool }foo(T)仅当T∈char,bool1.requires expressionrequires expression是定义概念concept的一种方式用于描述某个类型必须满足的操作或成员存在性。templateclassTconceptFooablerequires(T t){t.foo();// 类型 T 必须有一个 foo() 方法};上面代码定义了一个概念Fooable。requires (T t)中的语句是对类型 T 的约束。如果 T 不满足约束比如没有foo()方法编译时就会报错。注意requires expression并不是定义概念的唯一方法后面还有其他方式如布尔常量表达式或组合概念。2. 定义概念的基本方式概念可以通过多种方式约束模板参数布尔常量表达式#includeconceptstemplatetypenameTconceptByteSize(sizeof(T)1);// T 的大小必须为 1 字节templatetypenameTvoidfoo(T t)requiresByteSizeT{}intmain(){foo(a);// okfoo(false);// ok// foo(8); // fails// foo(); // fails}ByteSizeT是一个布尔表达式概念。满足条件的类型才能调用foo。组合概念Conjunction of concepts可以把已有概念和类型特性组合#includeconceptstemplatetypenameTconceptSignedByteSize(std::is_signed_vT(sizeof(T)1));templatetypenameTvoidfoo(T t)requiresSignedByteSizeT{}intmain(){foo(a);// okchar 是否有符号取决于编译器// foo(false); // fails// foo(8); // fails// foo(); // fails}SignedByteSize限制类型既是有符号类型又是1 字节大小。通过逻辑与可以组合多个约束条件。组合现有概念与类型操作可以将已有概念与类型值进行组合例如检查某个容器是否为特定元素类型#includevector#includeranges#includeconceptsusingnamespacestd::ranges;templatetypenameR,typenameEconceptRangeOfrangeRstd::same_asrange_value_tR,E;templateRangeOfintTvoidfoo(constTt){}intmain(){std::vectorintvec_int{1,2};std::vectorcharvec_char{a,b};foo(vec_int);// ok// foo(vec_char); // fails// foo(8); // fails// foo(); // fails}RangeOfR, E表示类型R是一个范围range并且元素类型是E。对于不符合条件的类型编译期直接报错。3. 概念的作用编译期约束类型保证模板安全性。避免传统 SFINAE 的复杂语法更直观清晰。可以组合不同约束实现高度灵活的模板接口。概念可用作requires子句或auto简化模板函数。4. 数学表示设一个概念C(T)则T 可以使用函数 foo ⟺ C(T)true T \text{ 可以使用函数 } foo \iff C(T) \text{true}T可以使用函数foo⟺C(T)true例如ByteSize(T)(sizeof(T)1) ByteSize(T) (sizeof(T) 1)ByteSize(T)(sizeof(T)1)SignedByteSize(T)(std::is_signed_vT∧sizeof(T)1) SignedByteSize(T) (\text{std::is\_signed\_vT} \wedge sizeof(T) 1)SignedByteSize(T)(std::is_signed_vT∧sizeof(T)1)调用foo(T)的条件就是概念约束成立。1. 基本语法概念可以通过requires requirements定义其中requirements是对类型或表达式的约束条件templateclassTconceptFooablerequiresrequirements;requirements可以采用两种形式花括号体Curly body不带参数仅说明类型必须满足的要求。带参数列表 花括号体要求类型 T 满足特定操作或成员函数存在。这里的arguments are unevaluated在requires中的参数不会真的求值只检查类型是否有相关成员或操作。2. 示例解析示例 1静态成员函数#includeconceptstemplatetypenameTconceptFooablerequires{T::foo();};templatetypenameTvoidfoo(T t)requiresFooableT{t.foo();}intmain(){structFoo{staticvoidfoo(){};};foo(Foo{});// ok// foo(8); // fails// foo(); // fails}requires { T::foo(); }表示T 类型必须有一个静态成员函数foo()。编译器仅检查T::foo()是否存在不会调用它。传入的Foo满足条件因此foo(Foo{})可以编译。其他类型如int或const char*不满足条件会在编译期报错。示例 2普通成员函数#includeconceptstemplatetypenameTconceptFooablerequires(T t){t.foo();};templatetypenameTvoidfoo(T t)requiresFooableT{t.foo();}intmain(){structFoo{voidfoo(){};};foo(Foo{});// ok// foo(8); // fails// foo(); // fails}requires(T t)表示T 类型必须有一个普通成员函数foo()。传入对象Foo{}可以调用成员函数foo()因此通过概念约束。对不满足条件的类型如int直接编译报错。示例 3使用std::declval#includeconcepts#includeutility// for std::declvaltemplatetypenameTconceptFooablerequires{std::declvalT().foo();};templatetypenameTvoidfoo(T t)requiresFooableT{t.foo();}intmain(){structFoo{voidfoo(){};};foo(Foo{});// ok// foo(8); // fails// foo(); // fails}std::declvalT()可以生成类型 T 的右值引用在编译期用于检查表达式有效性而无需构造对象。requires { std::declvalT().foo(); }作用与示例 2 相似但可以处理没有默认构造函数的类型。优点不需要实例化对象即可检查成员函数是否存在。3. 总结requires expression可以用于定义概念检查类型是否满足某些操作或成员函数存在。静态 vs 普通成员函数静态成员函数requires { T::foo(); }普通成员函数requires(T t) { t.foo(); }或requires { std::declvalT().foo(); }编译期检查约束在编译期生效不会真正调用成员函数。不满足约束的类型在模板实例化时直接报错。优点比 SFINAE 更直观可以灵活组合约束支持静态或动态成员函数检查4. 数学形式表示定义概念Fooable(T)Fooable(T)Fooable(T)Fooable(T){true如果 T 有成员函数 foo()false否则 Fooable(T) \begin{cases} true \text{如果 T 有成员函数 foo()} \\ false \text{否则} \end{cases}Fooable(T){truefalse​如果T有成员函数foo()否则​模板函数约束foo(T t) 可以调用 ⟺ Fooable(T)true \text{foo(T t)} \text{ 可以调用 } \iff Fooable(T) truefoo(T t)可以调用⟺Fooable(T)true类型 T -- 概念约束检查 -- 满足条件调用 / 不满足报错1. 概念主体的内容在概念体 (concept body) 中可以有多种约束形式未求值表达式Unevaluated expressions只需要能编译即可不会真正执行。用于检查类型是否存在某个成员函数或操作。内部requires子句用于要求类型满足一个布尔表达式。语法必须使用requires关键字。花括号表达式Curly-braced expressions可以用于注入到其他概念中。支持返回类型约束例如{T::bar()} - std::same_asint;表示T::bar()的返回值类型必须是int。内部类型约束Internal type existence要求类型必须有内部类型语法必须加typename。例如typename T::inner_type;检查T是否有嵌套类型inner_type。2. 示例解析templateclassTconceptAllSortOfChecksrequires{T::foo();// 1. 未求值表达式requiresT::Size2;// 2. 内部 requires 子句{T::bar()}-std::same_asint;// 3. 返回类型约束typenameT::inner_type;// 4. 内部类型约束};A 类型满足全部条件foo()存在Size 2bar()返回int有inner_type类型因此AllSortOfChecksA为trueB 类型不满足Size 2→AllSortOfChecksB为falseC 类型不满足bar()返回类型为int→AllSortOfChecksC为falseD 类型Size不是constexpr→ 概念检查无效3. 总结要点requires 关键字在概念中的两种用法requires clause在模板或函数声明上templatetypenameTrequiresSomeConceptT{...}requires expression概念内部conceptCrequires(T t){t.foo();requiresT::Size2;...};概念中表达式未求值检查类型或成员是否存在不会真正运行。适合检查静态成员、普通成员、内部类型或返回类型。返回类型约束使用花括号表达式{expr} - std::same_asType;指定返回类型。可以保证模板参数符合预期接口。内部类型约束typename T::inner_type;用于检查是否存在嵌套类型。必须加typename否则语法错误。4. 数学形式表示设概念AllSortOfChecks(T)定义如下AllSortOfChecks(T){true,如果满足下列条件1.存在静态成员函数 T::foo()2.T::Size23.T::bar() 返回类型为 int4.存在嵌套类型 T::innertypefalse,否则 AllSortOfChecks(T) \begin{cases} true, \text{如果满足下列条件} \\ 1. \text{存在静态成员函数 } T::foo() \\ 2. T::Size 2 \\ 3. T::bar() \text{ 返回类型为 } int \\ 4. \text{存在嵌套类型 } T::inner_type \\ false, \text{否则} \end{cases}AllSortOfChecks(T)⎩⎨⎧​true,false,​如果满足下列条件1.存在静态成员函数T::foo()2.T::Size23.T::bar()返回类型为int4.存在嵌套类型T::innert​ype否则​对每个类型 A、B、C、D可以代入判断是否满足条件从而决定static_assert是否通过。概念的模板参数 (The concept’s param)1. 概念总是模板化的每个概念 (concept) 至少要有一个模板参数。当概念直接引用某个类型时编译器可以自动注入第一个模板参数。示例// 1. 概念直接作用于模板类型参数templatePrintable Tvoidprint1(constTt){couttendl;}// 2. 概念作用于 auto 参数voidprint2(constPrintableautot){couttendl;}解释Printable T会让编译器自动推导T的类型。Printable auto t是 C20 中“缩写函数模板”abbreviated function template的语法效果类似于模板化函数但语法更简洁。2. 概念参数需要手动提供如果概念不直接依赖模板参数类型第一个参数不会被自动注入需要显式指定。示例templatetypenameTrequiresPrintableTvoidprint(constTt){couttendl;}ifconstexpr(PrintableT){...}static_assert(Printableint);// OK在以上情况中PrintableT必须显式写出类型T。3. 多参数概念一个概念可以有多个模板参数。第一个参数可以自动注入其余参数需要手动提供。示例Dereferenceable与DereferenceableTotemplatetypenameTconceptDereferenceablerequires(T t){*t;// t 必须可以解引用};templatetypenameT,typenameValueTypeconceptDereferenceableToDereferenceableTstd::same_asstd::remove_cvref_tdecltype(*std::declvalT()),ValueType;说明DereferenceableTT 类型必须可以进行*t操作。DereferenceableToT, ValueType除了可以解引用还要求解引用后的值类型与ValueType相同。4. 使用示例voidprint(constautov){coutSimple value: vendl;}voidprint(constDereferenceableautot){coutDereferenceable, inner value: *t, at: tendl;}voidprint(constDereferenceableTocharautot){coutDereferenceable to char: t, at: (void*)tendl;}intmain(){inti8;print(i);// Simple valueprint(i);// Dereferenceable, inner valueconstchar*shello;print(s);// DereferenceableTocharcharstr[]hi;print(str);// DereferenceableTochar}解释print(i)普通整型匹配第一个函数。print(i)指针类型匹配Dereferenceable auto。print(s)或print(str)字符指针或字符数组匹配DereferenceableTochar。概念参数注入机制编译器会自动推导auto类型并将其作为概念的第一个模板参数。5. 核心总结概念模板参数至少有一个模板参数。第一个参数可以自动注入。多参数概念中非第一个参数必须显式提供。用途对模板参数添加约束。简化 SFINAE 代码提高可读性。可在函数模板重载中精确匹配不同类型。实践建议尽量使用auto缩写函数模板简化语法。对复杂类型需求使用多参数概念。使用requires明确约束逻辑避免编译错误。练习 1TwopleFirst, Second目标希望定义一个概念TwopleFirst, Second可以约束一个类型是“包含两个元素的容器”并且第一个元素类型是First第二个元素类型是Second示例voidfoo(constTwopleint,doubleauto){...}能够匹配std::pairint, doublestd::tupleint, double出错原因你最初写的voidfoo(constTwopleint,doubleauto){...}报错Twople does not name a type原因C20 中概念必须先定义templateclass ... concept Twople ...;。概念可以带模板参数但要写成TwopleP, First, Second或使用auto语法时概念参数必须正确匹配函数模板的推导规则。正确实现示例#includeiostream#includetuple#includeconceptsusingstd::cout;usingstd::endl;// Twople 概念templatetypenameP,typenameFirst,typenameSecondconceptTwoplerequires(P p){requiresstd::tuple_sizeP::value2;// 必须有两个元素{std::get0(p)}-std::convertible_toFirst;// 第一个元素类型{std::get1(p)}-std::convertible_toSecond;// 第二个元素类型};// 使用概念voidfoo(constTwopleauto,int,doubleautot){coutTwopleint, double\n;}voidfoo(constauto){coutconst auto\n;}intmain(){std::pair p1{1,2.5};foo(p1);// Twopleint, doublestd::pair p2{1.5,2};foo(p2);// const autostd::tuple tup{1,2.5};foo(tup);// Twopleint, double}核心std::tuple_sizeP::value 2判断元素个数。std::get0(p)/std::get1(p)确认元素类型。使用auto和概念模板参数结合使类型推导自动完成。练习 2OneOfTs...目标定义一个概念OneOfTs...表示模板类型必须是指定类型之一voidfoo(constOneOfchar,int,longauto);voidfoo(constOneOfdouble,floatauto);出错原因之前报错OneOf does not name a type原因概念没有定义。auto参数与概念模板参数没有正确结合。C20 支持可变参数模板概念用于匹配任意类型列表。正确实现示例#includeconcepts#includeiostreamusingstd::cout;usingstd::endl;// OneOf 概念实现templatetypenameT,typename...TsconceptOneOf(std::same_asT,Ts||...);// fold expression// 使用概念voidfoo(constOneOfchar,int,longauto){coutOneOfchar, int, long\n;}voidfoo(constOneOfdouble,floatauto){coutOneOfdouble, float\n;}voidfoo(constauto){coutconst auto\n;}intmain(){foo(3.5);// OneOfdouble, floatfoo(4.5f);// OneOfdouble, floatstd::pair p{1,2};foo(p);// const autofoo(a);// OneOfchar, int, longfoo(1);// OneOfchar, int, longfoo(2L);// OneOfchar, int, longfoo(hello);// const auto}核心templatetypename T, typename... Ts concept OneOf (std::same_asT, Ts || ...);fold expression|| ...遍历可变模板参数列表判断类型是否匹配。将概念与auto参数结合实现模板函数的精确匹配。总结Twople约束容器类型的元素数量和类型。支持std::pair和std::tuple。OneOf用于多类型选择。使用 fold expression 判断类型是否匹配。C20概念与 auto 函数参数结合非常灵活可以实现类型安全的函数模板重载。避免复杂的 SFINAE 语法提高可读性。概念的非类型参数核心思想C20 中的概念 (concepts)第一个参数必须是类型type。后续参数可以是非类型参数如整数、枚举、指针等。这允许我们在概念中加入编译期常量约束实现更灵活的类型检查。示例分析templateclassT,size_t MIN_SIZE,size_t MAX_SIZEconceptSizeBetweensizeof(T)MIN_SIZEsizeof(T)MAX_SIZE;解释T类型参数MIN_SIZE、MAX_SIZE非类型参数size_t编译期常量条件MIN_SIZE≤sizeof(T)≤MAX_SIZE\text{MIN\_SIZE} \leq \text{sizeof}(T) \leq \text{MAX\_SIZE}MIN_SIZE≤sizeof(T)≤MAX_SIZE如果类型T的字节大小在[MIN_SIZE, MAX_SIZE]区间内则SizeBetweenT, MIN_SIZE, MAX_SIZE为true。函数模板结合概念voidfoo(constSizeBetween4,16auto){coutSizeBetween4, 16\n;}voidfoo(constSizeBetween17,32auto){coutSizeBetween17, 32\n;}// 所有其他情况voidfoo(constauto){coutconst auto\n;}分析SizeBetween4, 16 auto表示自动推导类型T前提是sizeof(T) ∈ [4, 16]SizeBetween17, 32 auto表示sizeof(T) ∈ [17, 32]其余类型通过普通auto匹配。main 函数中的调用intmain(){foo(4.5);// double, sizeof(double) 8 → 匹配 SizeBetween4,16std::pair p{1,2};// pairint,int, sizeof(pair) ≈ 16 → 匹配 SizeBetween4,16std::tuple tup{1,2.5,8};// tupleint,double,int, sizeof(tuple) 16 → 匹配 SizeBetween17,32?foo(a);// char, sizeof(char) 1 → 匹配 default auto}解释double→ 8 字节 →[4,16]→ 匹配第一个函数。std::pairint,int→ 假设 16 字节 → 匹配第一个函数。std::tupleint,double,int→ 大于 16 字节可能匹配第二个函数如果在 17~32 范围内。char→ 1 字节 → 匹配默认auto。总结概念参数设计第一个参数必须是类型。后续参数可以是常量或非类型参数用于编译期条件约束。使用场景限制类型大小限制数组长度限制枚举值函数模板匹配规则C20 会根据概念约束自动选择最匹配的模板。不满足任何概念的类型会匹配普通auto。目标实现一个概念TupleOfSIZE使得函数可以根据元组的元素数量Num_Elements进行重载voidfoo(constTupleOf2auto);// 元素数量为2voidfoo(constTupleOf3auto);// 元素数量为3要求可以匹配std::tuple和std::pair因为pair可视为 2 元素元组。对于非元组类型匹配普通auto。核心思路定义基本概念 Tuple检查类型是否有std::get0(t)可以访问。对空元组特殊处理。templateclassTconceptEmptyTuplestd::same_asT,decltype(std::tuple());// 空元组templateclassTconceptTupleEmptyTupleT||requires(T t){std::get0(t);// 至少可通过 std::get0 访问第一个元素};定义 TupleOf 概念在 Tuple 基础上增加元素数量约束templateclassT,size_t SIZEconceptTupleOfTupleTstd::tuple_size_vTSIZE;std::tuple_size_vT在编译期得到元组或 pair 的元素数量。使用TupleT保证类型确实是一个元组或类似元组的类型支持std::get。函数模板重载voidfoo(constTupleOf2autot){coutTupleOf2: std::get0(t) std::get1(t)\n;}voidfoo(constTupleOf3autot){coutTupleOf3: std::get0(t) std::get1(t) std::get2(t)\n;}voidfoo(constEmptyTupleauto){coutempty tuple\n;}// 所有非元组类型templatetypenameTrequires(!TupleT)voidfoo(constT){coutconst auto\n;}匹配规则TupleOf2 auto→ 匹配元素数量为 2 的元组或 pair。TupleOf3 auto→ 匹配元素数量为 3 的元组。EmptyTuple auto→ 匹配空元组。其它类型→ 匹配普通模板函数。main 测试intmain(){std::tuple t2{1,two};// 2 元素 → TupleOf2foo(t2);// 输出 TupleOf2: 1 twostd::tuple t3{1,two,3};// 3 元素 → TupleOf3foo(t3);// 输出 TupleOf3: 1 two 3std::pair p1{1,two};// pair → 2 元素 → TupleOf2foo(p1);// 输出 TupleOf2: 1 twofoo(7);// 非元组 → 输出 const autofoo(std::tuple());// 空元组 → 输出 empty tuple}核心特性总结概念可以有非类型参数TupleOfSIZE中SIZE是非类型模板参数。在编译期即可对元组长度进行约束。编译期重载选择编译器根据概念判断匹配函数模板。优先选择最精确匹配例如TupleOf2优于普通auto。灵活性可以扩展到任意元素数量。支持std::tuple、std::pair以及空元组。目标实现一个函数foo要求可以修改映射map中的值。不能修改映射本身例如不能插入或删除键。支持std::map和std::unordered_map。使用C20 concepts限制类型。核心问题普通 map 的operator[]会在键不存在时插入元素这会破坏“不修改 map 结构”的要求。因此只能使用finditerator-second来修改已有的值。Concept 定义templatetypenameTconceptConstDictMutableValsrequires(T t,typenameT::key_type key){typenameT::iterator;// 必须有迭代器类型typenameT::key_type;// 必须有 key_typetypenameT::mapped_type;// 必须有 mapped_type// t.find(key) 返回 T::iterator{t.find(key)}-std::same_astypenameT::iterator;// t.find(key) 可以和 t.end() 比较t.find(key)!t.end();};解释类型约束T必须有iterator、key_type、mapped_type类型。操作约束t.find(key)必须返回迭代器。可以与t.end()比较确保可以安全判断键是否存在。不允许使用operator[]避免修改 map 结构只允许通过迭代器修改值。函数实现voidfoo(ConstDictMutableValsautod){autoitrd.find(1);// 查找 key 1if(itr!d.end())// 如果存在itr-second3;// 修改值}这里仅修改已有键的值。使用find而非operator[]保证 map 结构不被修改。测试intmain(){mapint,intmyMap1{{1,0},{2,0}};foo(myMap1);// 修改 key1 的值for(constauto[k,v]:myMap1)std::coutk: vstd::endl;// 输出:// 1: 3// 2: 0unordered_mapint,intmyMap2{{1,0},{2,0}};foo(myMap2);// 同样生效for(constauto[k,v]:myMap2)std::coutk: vstd::endl;// 输出:// 1: 3// 2: 0}核心理解概念约束了类型和操作使用requires确保传入类型具备必要成员和操作。这是一种精细化类型接口约束类似静态接口Static Interface。可修改值不可修改容器结构通过限制不使用operator[]只允许finditerator-second。兼容性支持std::map、std::unordered_map等标准关联容器。安全与编译期检查如果传入不符合ConstDictMutableVals的类型编译期报错。提前避免运行时错误。小结关键点用requires表达“允许的操作”而不是暴露整个容器。概念约束帮助编译器在编译期检查类型的接口。这种模式适合最小权限接口设计只暴露函数需要的最小操作。1⃣ 问题背景原始做法Expression*enewSum(newExp(newNumber(3),newNumber(2)),newNumber(-1));cout*e e-eval()endl;deletee;问题手动管理内存newdelete不对称容易内存泄漏。表达式构造笨重嵌套new太长阅读困难。用户暴露实现细节用户必须知道底层用裸指针增加认知负担。2⃣ 使用智能指针改为autoestd::make_uniqueSum(std::make_uniqueExp(std::make_uniqueNumber(3),std::make_uniqueNumber(2)),std::make_uniqueNumber(-1));cout*e e-eval()endl;改进内存自动管理无需手动delete。避免裸指针泄漏。问题仍然存在语法仍然复杂嵌套make_unique太长。用户仍需要关心智能指针细节类型、移动语义。3⃣ 隐藏智能指针目标代码autoeSum(Exp(Number(3),Number(2)),Number(-1));coute e.eval()endl;改进点用户完全不关心智能指针。内部实现可自由选择存储策略unique_ptr或其他。构造语法简洁明了接近数学表达式。4⃣ 实现策略templatecharOpclassBinaryExpression:publicExpression{unique_ptrExpressione1,e2;// 私有实现隐藏细节virtualdoubleevalImpl(doubled1,doubled2)const0;public:templatetypenameExpression1,typenameExpression2BinaryExpression(Expression1 e1,Expression2 e2):e1(make_uniqueExpression1(std::move(e1))),e2(make_uniqueExpression2(std::move(e2))){}voidprint(ostreamout)constoverride{out(*e1 Op *e2);}doubleeval()constoverride{returnevalImpl(e1-eval(),e2-eval());}};核心设计思路私有智能指针成员用户不可访问隐藏了内存管理细节。模板构造函数可以接受任意Expression类型如Number,Sum,Exp。内部自动转换为unique_ptr。多态 eval调用evalImpl实现不同运算。保持接口统一。5⃣ 具体操作实现structSum:publicBinaryExpression{usingBinaryExpression::BinaryExpression;doubleevalImpl(doubled1,doubled2)constoverride{returnd1d2;}};structExp:publicBinaryExpression^{usingBinaryExpression^::BinaryExpression;doubleevalImpl(doubled1,doubled2)constoverride{returnstd::pow(d1,d2);}};特点using BinaryExpressionOp::BinaryExpression继承模板构造函数让派生类直接使用模板构造器。evalImpl实现具体运算逻辑。用户完全不用关心指针语法简洁。6⃣ 使用示例autoeSum(Exp(Number(3),Number(2)),Number(-1));coute e.eval()endl;// 输出: ((3 ^ 2) (-1)) 8优点总结隐藏实现细节用户不需要知道内部使用了unique_ptr。易于维护和修改将来可以换成shared_ptr或其他存储方式无需修改用户代码。语法清晰接近数学表达式书写方式。内存安全自动管理无需delete。7⃣ 限制与注意事项目前不能直接传递已有对象的左值例如autoe2Sum(e,Number(3));// 编译失败因为模板构造函数使用了std::move要求传入右值。可以进一步改进添加拷贝/移动构造支持使左值也可传递。小结本设计思想体现了“最小接口暴露 隐藏实现细节”的理念。利用了模板 unique_ptr 多态实现安全、简洁、可扩展的表达式树。1⃣ 隐藏继承层次细节Hiding Inheritance Hierarchies Details使用的设计模式State Pattern状态模式核心思想对象行为根据其内部状态变化。示例一个Employee无论是FullTimeEmployee、PartTimeEmployee或Contractor对外都是Employee类型具体行为由内部状态决定。Strategy Pattern策略模式核心思想算法或行为可以动态切换。示例PathFinder可使用 BFS 或 DFS 算法外部调用者只关心PathFinder不必关心使用哪种算法。Factory Method工厂方法核心思想封装对象创建过程让调用者不需要知道具体类型。用户只得到基类或接口类型即可。总结通过这些设计模式可以让外部使用者不关心继承体系的复杂性只依赖公共接口。2⃣ 减少返回值信息量Conveying less information on return values示例 1直接返回具体类型vectorintfoo1(){returnvector{1,2,4};}优点明确返回类型。缺点对调用者暴露了内部实现细节必须是vectorint。限制了将来可能替换成其他容器如std::arrayint,3或std::dequeint。示例 2使用auto返回autofoo2(){returnvector{1,2,4};}优点隐藏了具体类型调用者不必关心返回容器类型。以后可以修改返回类型而不破坏接口。缺点对于使用者无法静态限制返回类型特性例如随机访问。示例 3使用概念约束返回类型templatetypenameT,typenameElementTypeconceptrandom_access_range_ofstd::ranges::random_access_rangeTstd::same_asstd::ranges::range_value_tT,ElementType;random_access_range_ofintautofoo3(){returnvector{1,2,4};}优点隐藏具体类型调用者不关心容器是vector还是array。保留必要特性保证返回类型是随机访问范围(random_access_range) 并且元素类型是int。接口清晰使用概念表达“我需要什么性质的返回值”而不是具体类型。缺点需要 C20 概念支持。对于简单函数可能略显复杂。3⃣ 总结对比方法隐藏信息类型限制灵活性vectorint暴露具体类型明确不灵活容器固定auto隐藏类型无法限制特性灵活可更换容器concept auto隐藏类型保证性质如随机访问灵活且安全核心思想使用auto 概念可以在隐藏实现细节的同时保留静态约束兼顾安全与灵活性。适合公共接口设计尤其在库开发中。1⃣ 使用概念隐藏返回值类型Conveying less information on return values核心点概念作为返回值类型C20 支持random_access_range_ofintautofoo(){...}自动类型推导auto必须在函数体内实现header 中。编译器不会检查使用者的操作是否完全符合概念限制只保证返回值在概念约束下可用。接口隐藏与 Hyrum’s Law如果暴露太多实现细节调用者可能依赖这些细节造成leaky abstraction。Hyrum’s Law: “任何你允许的使用者行为都会被依赖”意味着即便你只想隐藏内部实现用户也可能依赖暴露的细节导致维护难度增加。实现隐藏的方式概念返回类型C20优点类型隐藏仍可表达静态约束。缺点需要头文件内实现编译器对使用者行为不做检查。接口 虚函数动态派发隐藏具体类型。缺点引入运行时开销。包装类Wrapper Class可封装复杂类型或行为。优点更灵活仍可隐藏实现细节。缺点工作量稍大。注意“泄漏的抽象”典型例子std::vectorbool返回的 proxy 引用。用户可能依赖返回 proxy 的行为而不是仅仅使用 bool破坏了抽象封装。2⃣ 隐藏辅助函数Hiding Helper Functions问题示例classThingy{public:voidfoo();// 注意不要在调用 foo 之前调用 bar!voidbar();};foo与bar有调用顺序依赖。用户可以错误地直接调用bar导致使用错误。尝试方案 1组合公共函数classThingy{voidfoo();voidbar();public:voidfoobar(){foo();bar();}};优点用户只能调用foobar()内部顺序固定。缺点如果想动态条件执行bar没有灵活性。尝试方案 2模板函数封装classThingy{voidfoo();voidbar();public:templatetypenameFuncvoidfoobar(Funcfunc){foo();if(func())bar();}};优点用户只能调用foobar()顺序安全。可以传入 lambda 决定是否调用bar()增加灵活性。结合示例#includeiostreamclassThingy{voidfoo(){std::coutfoostd::endl;}voidbar(){std::coutbarstd::endl;}public:templatetypenameFuncvoidfoobar(Funcfunc){foo();if(func())bar();}};intmain(){Thingy t;t.foobar([]{returntrue;});}输出foo bar内部实现细节被完全隐藏调用者不能误用foo或bar。总结隐藏返回值类型用概念、接口或包装类隐藏实现避免泄露具体类型。注意 Hyrum’s Law用户可能依赖暴露的行为。隐藏辅助函数将多个相关函数封装到单个公共接口中。可使用模板 回调函数实现灵活控制同时保证调用顺序安全。典型模式命令封装 回调。设计理念封装内部实现让接口保持简单。避免用户依赖内部实现细节减少维护风险。灵活性与安全性的平衡是关键。1⃣ 核心思想这个方案通过返回一个临时对象临时类型BarCallable来隐藏辅助函数的调用顺序和访问限制。classThingy{voidinner_foo(){std::coutfoostd::endl;}voidbar(){std::coutbarstd::endl;}public:autofoo(){inner_foo();// 内部先执行 foo 的逻辑structBarCallable{Thingy*t;voidbar(){t-bar();}// 只能通过临时对象调用 bar};returnBarCallable(this);}};inner_foo()是私有函数用户不能直接调用。bar()也是私有函数用户不能直接调用。foo()是公共接口调用后返回一个局部 struct 类型对象BarCallable。用户想调用bar()时只能通过foo()返回的BarCallable对象t.foo().bar();// 调用顺序被强制必须先调用 foo()如果用户只是调用t.foo();只执行inner_foo()bar()不会被调用。2⃣ 特性和优点强制调用顺序用户不能直接调用bar()。bar()只能通过foo()返回的对象调用保证了bar()前必先执行inner_foo()。隐藏内部实现inner_foo()和bar()都是私有的实现细节不暴露给用户。可链式调用通过返回临时对象可写成t.foo().bar();灵活性用户可以选择只调用foo()t.foo();// 只执行 inner_foo()或者调用bar()t.foo().bar();// 执行 inner_foo() 后执行 bar()3⃣ 与前面模板回调方法对比特性模板回调方法临时对象返回方法隐藏顺序调用模板回调保证顺序返回对象调用保证顺序灵活性回调可控制是否执行 bar可选择是否调用返回对象的 bar编译时类型依赖编译时确定是否调用 bar编译时确定 BarCallable 类型代码可读性中等高可链式调用接口清晰私有函数保护内部函数不暴露内部函数不暴露4⃣ 使用示例intmain(){Thingy t;t.foo().bar();// 输出: foo bart.foo();// 输出: foo}输出顺序foo bar foo符合预期bar()只能在foo()执行后被调用。用户无法错误地单独调用bar()或inner_foo()。5⃣ 总结这种“返回临时对象控制访问”的方法是一种安全封装辅助函数的高级技巧让内部实现细节保持私有。强制执行调用顺序。支持链式调用和可选调用。接口对用户更直观、易用同时内部逻辑安全。可以看作是“临时代理对象模式”Temporary Proxy Object Pattern在 C 中非常适合隐藏实现和约束调用顺序。1⃣ 使用protected的原则与技巧1.1 数据成员尽量私有classParent{Wallet wallet;// 私有protected:shortneed_money(longdouble,conststd::stringreason);// 受保护};原则数据成员应尽量声明为private避免子类直接访问。受保护成员函数protected可被子类调用但不对外公开。对比classParent{protected:Wallet wallet;// 不推荐};这种方式会暴露内部状态给子类违反封装原则。1.2 调用示例classChild:publicParent{public:voidendless_celebration(longdoubleamount){need_money(amount,celebration);// 安全访问受保护方法}};子类通过protected方法访问内部状态或逻辑而不直接暴露数据成员。2⃣ 测试私有行为的建议2.1 尽量避免直接测试私有数据私有状态如类在状态 A通常不应直接测试。如果必须测试不要将成员改为public。可以添加公共验证方法用于状态检查或日志跟踪。2.2 不通过测试修改私有状态测试中直接修改私有数据通常是不好的不测试实际流程。可能导致测试不稳定或不真实。如果测试难以实现可能说明代码耦合过高需要重构或设计更好的解耦。2.3 可行方法使用模拟mocking或构造错误场景通过正常接口触发状态变化而不是直接修改私有数据。如果仍需访问私有成员可以通过友元测试或特定访问接口而不是直接暴露数据。3⃣ 使用命名空间和上下文隐藏实现利用命名空间和嵌套类型隐藏类型和实现细节。私有嵌套类型可用于内部代理对象。C20 模块提供更强的隐藏能力可以隐藏模板实现。避免过多暴露内部类型。4⃣ Private Token Idiom私有令牌模式4.1 问题想让构造函数私有化但仍希望使用make_unique创建对象classFoo{public:Foo(intvalue):value(value){}// 想私有化staticstd::unique_ptrFoocreate(intvalue){returnstd::make_uniqueFoo(value);}};直接私有化构造函数会导致make_unique编译失败。4.2 解决方案私有令牌模式定义一个私有嵌套类型PrivateTokenclassFoo{intvalue;classPrivateToken{};// 私有令牌public:Foo(intvalue,PrivateToken):value(value){}// 仅接受令牌构造staticstd::unique_ptrFoocreate(intvalue){returnstd::make_uniqueFoo(value,PrivateToken{});// 使用令牌创建}intgetValue()const{returnvalue;}};用户不能直接构造Foo// Foo f(7); // 编译错误必须通过create工厂函数创建autofooFoo::create(7);// 安全创建4.3 优点隐藏实现细节构造函数仅可通过私有令牌访问。强制工厂函数使用确保对象创建逻辑一致。避免接口泄露用户看不到内部实现或构造方式。5⃣ 总结protected数据成员尽量private。函数可适当protected提供给子类。测试私有行为避免直接测试私有数据。使用公共接口或模拟。命名空间与模块提供上下文隐藏内部实现。Private Token Idiom工厂函数模式 私有令牌。隐藏构造函数实现强制安全对象创建。这个模式非常适合想隐藏构造逻辑或敏感接口强制使用工厂函数创建对象保持类的封装性与安全性1⃣ 隐藏实现细节的重要性核心思想隐藏实现细节不仅仅是设计建议更是提高代码健壮性和可维护性的重要手段。好的隐藏可以降低外部对内部实现的依赖。内部修改不会影响外部代码。提高调试和测试的便利性。2⃣ 隐藏规则与设计原则的关系2.1 封装 (Encapsulation)保护对象完整性只暴露必要接口。内部变化不影响外部使用。示例数据成员尽量private。提供protected或公共函数来访问内部逻辑而不是暴露内部数据。2.2 解耦 (Decoupling)组件只依赖于必要接口。系统各部分修改不会传导影响。组件更通用测试更容易。使用概念、接口、工厂函数、私有令牌模式等都属于解耦策略。3⃣ 私有字段并不够私有字段是必要条件但不是充分条件。为什么上下文决定了哪些内容应该被公开。如果不需要就不提供。一旦提供用户可能会滥用。原则Trust no one! \text{Trust no one!}Trust no one!不假设用户会按规矩使用接口。4⃣ 保持抽象 (Keep Abstractions)抽象 ≠ 模糊。抽象的目的是创建新的语义层次在更高层次上精确表达概念。引用 Dijkstra 的观点The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.应用使用接口、概念或工厂函数隐藏实现。使用私有类型、嵌套类、模块化等手段控制可见性。5⃣ 避免错误理由暴露内部实现有些开发者会为了“方便”而公开内部实现但大多数理由都是错误的。正确做法仔细判断哪些内容不应该暴露。保持接口最小化和精确。提前预防未来的缺陷和不必要的重构。6⃣ 小结原则封装优先私有字段、受保护方法、公共接口。解耦组件只暴露必需的行为。上下文敏感不同使用场景可能需要不同可见性。保持抽象抽象提供精确语义而不是模糊表达。最小暴露如果不需要就不要公开。工具支持命名空间、嵌套类型、C20 模块、概念、私有令牌模式等。7⃣ 实际代码示例私有令牌模式Private Token IdiomclassFoo{intvalue;classPrivateToken{};// 私有令牌Foo(intvalue,PrivateToken):value(value){}// 构造函数仅可由令牌调用public:staticstd::unique_ptrFoocreate(intvalue){returnstd::make_uniqueFoo(value,PrivateToken{});}intgetValue()const{returnvalue;}};autofooFoo::create(7);// 安全创建隐藏构造函数实现。强制通过工厂函数创建对象。避免滥用和接口泄露。总结隐藏实现细节的目标是安全、可维护、可复用的代码。私有字段和保护成员只是基础。抽象、工厂、概念和模块是高级工具。保持接口最小化暴露必要行为才能有效控制复杂性。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

如何做英文网站推广免费外链工具

01 生活哪有那么多弯弯绕, 无非是干饭香、躺平爽, 给自个儿整点儿乐子, 给朋友唠句玩笑。 忙到飞起不抓狂,闲下来不瞎慌, 鸡毛蒜皮的日子里, 怎么舒坦怎么浪~ 02 你瞎操心的事儿,八…

张小明 2025/12/19 16:36:01 网站建设

做门户网站需要准备什么wordpress关键词在哪

如果你和我的团队一样,长期维护着一个庞大却脆弱的 UI 自动化测试脚本库,一定对这样的场景再熟悉不过:前端一次看似微小的改动——可能只是一个 CSS 类名变更,或组件结构的轻微调整——就足以让大量测试脚本集体失效。修复它们不仅…

张小明 2025/12/19 16:35:00 网站建设

芜湖公司企业排名网站权重优化方式

虚实融合,数字人开启智能交互新纪元随着人工智能、图形渲染与大数据技术的深度融合,AI数字人已从概念演示快速演进为驱动产业数字化与社会服务智能化变革的关键力量。它不再是简单的动画形象,而是集成了自然语言理解、语音交互、情感计算与高…

张小明 2025/12/19 16:33:59 网站建设

网站开发总监待遇wordpress网站分享朋友圈缩略图

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个电商大促场景的JMeter测试模板,模拟高并发用户登录、商品浏览、下单支付等核心流程。要求支持参数化用户数据、动态关联接口响应、分布式测试部署,并…

张小明 2025/12/19 16:33:35 网站建设