破防了!纲手“黑料”流出?某M站争议事件背后的二次元狂欢与幻灭
std::variantstd::variant是 C17 æ ‡å‡†åº“ä¸åŠ å…¥çš„ä¸€ä¸ªç±»æ¨¡æ�¿å®ƒä»£è¡¨ä¸€ä¸ªç±»å�‹å®‰å…¨çš„è�”å�ˆä½“union。它å�¯ä»¥æŒ�有其模æ�¿å�‚æ•°åˆ—è¡¨ä¸æŒ‡å®šçš„任何一ç§�ç±»å�‹çš„值。我们也ä¸�å�«ä»–è�”å�ˆä½“了常说的便是“å�˜ä½“â€� å�‚考https://en.cppreference.com/w/cpp/header/variant.htmlä¼ ç»Ÿçš„ Cunionä¸�是类å�‹å®‰å…¨çš„。我们需è¦�自己记ä½�当å‰�å˜å‚¨çš„æ˜¯å“ªç§�ç±»å�‹å¦‚æ�œè®¿é—®é”™äº†æ¯”如在一个å˜å‚¨intçš„union上读å�–float会导致未定义行为就好比内å˜é‡Œå®�际是int 10的二进制数æ�®ä½†ä½ è¦�求编译器按照floatçš„æ ¼å¼�è§£æ��这段二进制 ——floatå’Œint的二进制编ç �规则完全ä¸�å�Œæ¯”如float是 IEEE 754 æµ®ç‚¹æ ¼å¼�int是补ç �è§£æ��出æ�¥çš„结æ�œæ˜¯æ— æ„�义的 “å�ƒåœ¾å€¼â€�è€Œä¸”å®ƒæ— æ³•å¤„ç�†é��平凡类å�‹å¦‚std::stringå…¶ä¸é��平凡类å�‹æŒ‡é‚£äº›æœ‰è‡ªå®šä¹‰æ�„é€ å‡½æ•° / æ��æ�„函数ã€�自定义拷è´� / 移动è¯ä¹‰ã€�虚函数的类å�‹æ¯”如std::stringã€�std::vectorã€�std::mapç‰è¿™ç±»ç±»å�‹çš„对象需è¦�编译器自动管ç�†èµ„æº�比如std::stringä¼šåœ¨å †ä¸Šåˆ†é…�内å˜å˜å‚¨å—符æ��æ�„时释放。详细的对比就是C11 之å‰�union 完全ç¦�æ¢åŒ…å�«é��平凡类å�‹ —— å› ä¸º union çš„æ�„é€ / æ��æ�„函数是编译器自动生æˆ�的它å�ªä¼šåˆ†é…�内å˜ä½†ä¸�会调用æˆ�员的æ�„é€ / æ��æ�„函数。比如// C11 å‰�编译报错 union BadUnion { int i; std::string s; // std::string 有自定义æ�„é€ /æ��æ�„/æ‹·è´� };如æ�œå…�许BadUnionçš„æ��æ�„函数ä¸�知é�“该调用int还是std::stringçš„æ��æ�„函数std::stringçš„å †å†…å˜ä¼šæ³„æ¼�导致资æº�管ç�†å´©æºƒã€‚C11 å�Šä¹‹å��å…�许包å�«é��平凡类å�‹ä½†éœ€è¦�手动管ç�†æ�„é€ / æ��æ�„æ��å…¶ç¹�ç��且容易出错#include iostream #include string union StringUnion { int i; std::string s; // 空æ�„é€ ä¸�åˆ�始化任何æˆ�员必须手动æ�„é€ StringUnion() {} // 空æ��æ�„ä¸�销æ¯�任何æˆ�员必须手动销æ¯� ~StringUnion() {} // 手动æ�„é€ std::string æˆ�员 void init_string(const std::string str) { // 定ä½� new在 s 对应的内å˜åœ°å�€ä¸Šæ�„é€ std::string 对象 new (s) std::string(str); } // 手动销æ¯� std::string æˆ�员 void destroy_string() { // 显å¼�调用 std::string çš„æ��æ�„å‡½æ•°é‡Šæ”¾å †å†…å˜ s.~basic_string(); } }; int main() { // 场景1æ£ç¡®ä½¿ç”¨å…ˆæ�„é€ ã€�å†�使用ã€�最å��销æ¯� StringUnion u1; //
手动æ�„é€ string æˆ�员必须先æ�„é€ æ‰�能访问 u
init_string(Hello Union); //
使用 string �员 std::cout u
s u
s std::endl; // 输出Hello Union //
手动销æ¯� string æˆ�员必须销æ¯�å�¦åˆ™å†…å˜æ³„æ¼� u
destroy_string(); // 场景2切æ�¢æˆ�员先销æ¯�æ—§æˆ�员å†�æ�„é€ æ–°æˆ�员 StringUnion u2; // 先使用 int æˆ�员int 是平凡类å�‹æ— 需手动æ�„é€ /销æ¯� u
i 100; std::cout u
i u
i std::endl; // 输出100 // 切æ�¢åˆ° string æˆ�员int æ— éœ€é”€æ¯�但必须先æ�„é€ string u
init_string(Switch to string); std::cout u
s u
s std::endl; // 输出Switch to string u
destroy_string(); // 用完必须销æ¯� // 场景3错误示例忘记销æ¯�å¯¼è‡´å†…å˜æ³„æ¼� // StringUnion u3; // u
init_string(Memory Leak); // // 忘记调用 destroy_string()std::string çš„å †å†…å˜æ°¸è¿œä¸�会释放 // 场景4错误示例未æ�„é€ å°±è®¿é—®æœªå®šä¹‰è¡Œä¸º // StringUnion u4; // std::cout u
s std::endl; // 未æ�„é€ å°±è®¿é—®ç¨‹åº�崩溃/å�ƒåœ¾å€¼ return 0; }细节点new (s) std::string(str)调用定ä½� newä¸�åœ¨å †ä¸Šåˆ†é…�新内å˜è€Œæ˜¯ç›´æ�¥åœ¨s这个地å�€ä¸Šæ�„é€ std::string对象执行std::stringçš„æ�„é€ å‡½æ•°åˆ�始化其内部的指针ã€�é•¿åº¦ç‰æˆ�员。这ç§�写法需è¦�ä½ æ‰‹åŠ¨è®°ä½� “当å‰�活跃的是哪个æˆ�员â€�手动调用æ�„é€ / æ��æ�„ —— ä¸�仅代ç �å¤�æ�‚还å›�到了 “记类å�‹â€� 的问题一旦æ¼�调用æ��æ�„å°±ä¼šå¯¼è‡´å†…å˜æ³„æ¼�调用错了å�ˆæ˜¯æœªå®šä¹‰è¡Œä¸ºã€‚std::variant的优势是它解决了所有这些问题它知é�“当å‰�å˜å‚¨çš„æ˜¯å“ªç§�ç±»å�‹å¹¶ç¡®ä¿�对象被æ£ç¡®æ�„é€ å’Œæ��æ�„我们å�¯ä»¥æŠŠå®ƒæƒ³è±¡æˆ�一个 “智能的â€�ã€�“类å�‹ä¸°å¯Œçš„â€�union。ï¸� 定义和赋值修改#include variant #include string #include iostream // 示例定义和赋值 int main() { // 定义一个 variant它å�¯ä»¥å˜å‚¨ä¸€ä¸ª int一个 double或一个 std::string std::variantint, double, std::string v; v 42; // ç�°åœ¨æŒ�有 int std::cout int: std::getint(v) std::endl; v
14; // ç�°åœ¨æŒ�有 double std::cout double: std::getdouble(v) std::endl; v hello; // ç�°åœ¨æŒ�有 std::string std::cout string: std::getstd::string(v) std::endl; // 赋值时如æ�œæ‰¾ä¸�到对应类å�‹çš„值则报错 // v std::pairint, int{}; // Error // 使用index()è�·å�–当å‰�æŒ�有的类å�‹ç´¢å¼• std::cout Current index: v.index() std::endl; std::variantstd::string, std::string v2; // v2 abc; // Error }std::variant是 C17 引入的类å�‹å®‰å…¨è�”å�ˆä½“在定义时必须指定它能å˜å‚¨çš„æ‰€æœ‰ç±»å�‹åˆ—表且这些类å�‹ä¼šæŒ‰é¡ºåº�分é…�索引ä»� 0 开始。// æ ¼å¼�std::variantç±»å�‹1, ç±»å�‹2, ç±»å�‹3, ... å�˜é‡�å��; std::variantint, double, std::string v; // å�¯å˜å‚¨int(索引
�double(索引
�string(索引
定义时至少è¦�指定一ç§�ç±»å�‹ç©ºçš„std::variant是é��法的ä¸�å…�许é‡�å¤�定义相å�Œç±»å�‹å¦‚std::variantstd::string, std::stringè¿™ç±»å®šä¹‰æ— æ„�义且会导致编译错误未显å¼�赋值时std::variant会默认åˆ�始化第一个类å�‹å¦‚上é�¢çš„våˆ�å§‹æŒ�有值为0çš„int。所以我们就å�•纯定义的è¯�也会调用æ�„é€ æ‰€ä»¥è¦�求第一个å�‚æ•°å¿…é¡»è¦�有默认æ�„é€ ç»†èŠ‚ä¸�è¦�错如æ�œéƒ½æ²¡æœ‰é»˜è®¤æ�„é€ æˆ‘ä»¬å�¯ä»¥ç¬¬ä¸€ä¸ªä¼ 入一个该类æ��供的一个空类 --- std::monostatestd::variant支æŒ�ç›´æ�¥èµ‹å€¼ä½†å�ªèƒ½èµ‹å€¼ä¸ºå®šä¹‰æ—¶æŒ‡å®šçš„ç±»å�‹èµ‹å€¼å��会自动切æ�¢å†…部å˜å‚¨çš„ç±»å�‹ã€‚std::variantint, double, std::string v; //
赋值为int类�索引0 v 42; std::cout int值: std::getint(v) std::endl; // 输出int值: 42 //
赋值为double类�索引1 v
14; std::cout double值: std::getdouble(v) std::endl; // 输出double值:
14 //
赋值为std::stringç±»å�‹ç´¢å¼•2 v hello; // å—é�¢é‡�自动转æ�¢ä¸ºstd::string std::cout string值: std::getstd::string(v) std::endl; // 输出string值: helloâ�Œ ä¸�能赋值为定义时未指定的类å�‹å¦‚v std::pairint, int{}会直æ�¥ç¼–译报错â�Œ é‡�å¤�ç±»å�‹çš„std::variant如std::variantstd::string, std::stringæ— æ³•èµ‹å€¼å› ä¸ºç¼–è¯‘å™¨æ— æ³•åŒºåˆ†é‡�å¤�ç±»å�‹âœ… 赋值时会自动处ç�†ç±»å�‹è½¬æ�¢å¦‚const char*å—é�¢é‡�å�¯èµ‹å€¼ç»™std::stringç±»å�‹çš„å�˜ä½“。通过index()æˆ�员函数å�¯è�·å�–当å‰�å˜å‚¨ç±»å�‹çš„索引验è¯�赋值是å�¦æˆ�功切æ�¢ç±»å�‹std::variantint, double, std::string v; v hello; // 切æ�¢ä¸ºstringç±»å�‹ç´¢å¼•2 std::cout 当å‰�ç±»å�‹ç´¢å¼•: v.index() std::endl; // 输出当å‰�ç±»å�‹ç´¢å¼•: 2索引ä»� 0 开始ä¸�定义时的类å�‹é¡ºåº�ä¸¥æ ¼å¯¹åº”è‹¥std::variant处äº� “空状æ€�â€�如异常情况下index()会返å›�std::variant_npos通常是size_t最大值。 访问值
使用std::getT或std::getN我们å�¯ä»¥é€šè¿‡ç±»å�‹æˆ–索引æ�¥ç±»å�‹æ¨¡æ�¿å�‚æ•°/é��ç±»å�‹æ¨¡æ�¿å�‚æ•°ç›´æ�¥è�·å�–值。但如æ�œå½“å‰�variantå˜å‚¨çš„ä¸�是我们请求的类å�‹ / 索引它会抛出std::bad_variant_access异常。int main() { std::variantint, double v 42; try { std::cout std::getint(v) std::endl; std::cout std::getdouble(v) std::endl; // 抛出异常 } catch (const std::bad_variant_access e) { std::cout Error: e.what() std::endl; } }
使用std::get_ifTstd::get_ifä¸�会抛出异常。它æ�¥å�—一个指针å�‚数如æ�œvariant当å‰�å˜å‚¨çš„æ˜¯æŒ‡å®šç±»å�‹åˆ™è¿”å›�一个指å�‘该值的指针å�¦åˆ™è¿”å›�nullptr。int main() { std::variantint, double, std::string v hello; // 使用std::get_ifå°�试è�·å�–值 if (auto pval std::get_ifint(v)) { std::cout int value: *pval std::endl; } else if (auto pval std::get_ifdouble(v)) { std::cout double value: *pval std::endl; } else if (auto pval std::get_ifstd::string(v)) { std::cout string value: *pval std::endl; } }
使用std::visitæ�¨è��最安全强大std::visit类模æ�¿å…�è®¸ä½ æ��供一个 “访问者â€�visitoræ�¥æ ¹æ�®å½“å‰�å˜å‚¨çš„ç±»å�‹æ‰§è¡Œç›¸åº”çš„æ“�作这是最类å�‹å®‰å…¨ã€�最清晰的方å¼�。第一个å�‚数访问者是一个å�¯è°ƒç”¨å¯¹è±¡é€šå¸¸æ˜¯ä¸€ä¸ªé‡�载了operator()的类或者使用 lambda 表达å¼�结å�ˆoverloaded技巧std::visit会把std::variant对象ä¸å˜å‚¨çš„值å�–出æ�¥ä½œä¸ºå�‚æ•°ä¼ ç»™ visitor å�¯è°ƒç”¨å¯¹è±¡ã€‚所以在处ç�†std::variantæ—¶std::visit是更ç�°ä»£ã€�æ›´å®‰å…¨ä¹Ÿæ›´å¼ºå¤§çš„é€‰æ‹©ç›¸æ¯”ä¼ ç»Ÿçš„std::getå’Œstd::get_if它在代ç �çš„å�¥å£®æ€§ã€�å�¯ç»´æŠ¤æ€§å’Œè¡¨è¾¾åŠ›ä¸Šéƒ½æœ‰æ˜�显优势。#include iomanip #include iostream #include string #include type_traits #include variant #include vector // the variant to visit using value_t std::variantint, double, std::string; struct VisitorOP { void operator()(int i) const { std::cout int: i \n; } void operator()(double d) const { std::cout double: d \n; } void operator()(const std::string s) const { std::cout string: s \n; } }; // helper type for the visitor #4 templateclass... Ts struct overloaded : Ts... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C
templateclass... Ts overloaded(Ts...) - overloadedTs...; int main() { std::vectorvalue_t vec { 10,
5, hello }; for (auto v : vec) { std::visit(VisitorOP(), v); } std::cout \n; for (auto v : vec) { //
void visitor, only called for side-effects (here, for I/O) std::visit([](auto arg) { std::cout arg; }, v); //
value-returning visitor, demonstrates the idiom of returning // another variant value_t w std::visit([](auto arg) - value_t { return arg arg; }, v); //
type-matching visitor: a lambda that handles each type // differently std::cout . After doubling, variant holds ; std::visit([](auto arg) { using T std::decay_tdecltype(arg); if constexpr (std::is_same_vT, int) std::cout int with value arg \n; else if constexpr (std::is_same_vT, double) std::cout double with value arg \n; else if constexpr (std::is_same_vT, std::string) std::cout string with value \ std::quoted(arg) \\n; else static_assert(false, non-exhaustive visitor!); }, w); } std::cout \n; for (auto v : vec) { //
another type-matching visitor: a class with 3 overloaded // operator()s std::visit(overloaded{ [](int arg) { std::cout int: arg ; }, [](double arg) { std::cout double: arg ; }, [](const std::string arg) { std::cout string: std::quoted(arg) ; } }, v); } }std::get/std::get_if需è¦�ä½ æ‰‹åŠ¨ä¿�è¯�访问的类å�‹æˆ–索引ä¸�variant当å‰�å˜å‚¨çš„ç±»å�‹ä¸€è‡´ã€‚如æ�œç±»å�‹ä¸�匹é…�std::get会抛出异常std::get_if会返å›�nullptr但这些错误都å�‘生在è¿�行时。std::visitç¼–è¯‘å™¨ä¼šå¼ºåˆ¶ä½ å¤„ç�†variant䏿‰€æœ‰å�¯èƒ½çš„ç±»å�‹ã€‚如æ�œæ¼�æ�‰äº†ä»»ä½•一ç§�ç±»å�‹ä»£ç �会在编译期就报错比如代ç �里的static_assert(false, non-exhaustive visitor!)ä»�æ ¹æº�上æ�œç»�了è¿�行时错误。而且性能开销visit 是比较å°�的没有è¿�行时的检查类å�‹
基础背景代ç �çš„æ ¸å¿ƒç›®æ ‡è¿™æ®µä»£ç �先定义了一个能å˜å‚¨int/double/stringçš„variantç±»å�‹value_tç„¶å��创建了包å�«è¿™ä¸‰ç§�ç±»å�‹å€¼çš„å�‘é‡�vec。整个程åº�çš„æ ¸å¿ƒå°±æ˜¯ç”¨std::visité��å�†è¿™ä¸ªå�‘é‡�对æ¯�个variant里的ä¸�å�Œç±»å�‹å€¼æ‰§è¡Œä¸�å�Œæ“�作 —— 本质是 â€œæ ¹æ�®variantå˜å‚¨çš„å®�é™…ç±»å�‹è‡ªåŠ¨è°ƒç”¨å¯¹åº”é€»è¾‘â€�。
第一ç§�用法用自定义结æ�„体å�š “访问者â€�代ç �里的VisitorOP是一个结æ�„体它é‡�载了 3 次operator()分别处ç�†int/double/stringç±»å�‹ã€‚std::visit(VisitorOP(), v)就是把v里å˜å‚¨çš„å€¼ä¼ ç»™VisitorOP的对象编译器会自动匹é…�值的类å�‹è°ƒç”¨å¯¹åº”çš„operator()比如vå˜çš„æ˜¯int就调用处ç�†int的那个函数最终打å�°å¯¹åº”ç±»å�‹å’Œå€¼ã€‚这是std::visit最基础的用法用 “é‡�载函数调用è¿�算符的类â€� å°�装所有类å�‹çš„处ç�†é€»è¾‘。
第二ç§� 第三ç§�用法用 lambda å�šè®¿é—®è€…进阶第二段循ç�¯é‡Œç¬¬ä¸€ä¸ªstd::visitç›´æ�¥ä¼ 了一个泛å�‹ lambda[](auto arg) { std::cout arg; }å› ä¸º lambda 是泛å�‹çš„能æ�¥æ”¶ä»»æ„�ç±»å�‹çš„å�‚数所以å�¯ä»¥ç›´æ�¥å¤„ç�†variant里的所有类å�‹ç¬¬äºŒä¸ªstd::visit更巧妙它先返å›�一个新的variant把å�Ÿå€¼ç¿»å€�比如 int 10 å�˜ 20string hello å�˜ hellohelloç„¶å��å�ˆç”¨ä¸€ä¸ªå¸¦if constexpr的泛å�‹ lambda在编译期判æ–å�‚æ•°ç±»å�‹åˆ†åˆ«æ‰“å�°ä¸�å�Œçš„æ��ç¤ºè¯ â€”â€” æ ¸å¿ƒæ˜¯ “用泛å�‹ lambda ç¼–è¯‘æœŸåˆ¤æ–æ›¿ä»£ç»“æ�„体é‡�è½½â€�。
第四ç§�用法用overloaded组å�ˆå¤šä¸ª lambda最优å®�践代ç �里的overloaded是一个模æ�¿æŠ€å·§å®ƒèƒ½æŠŠå¤šä¸ªä¸�å�Œçš„ lambda “å�ˆå¹¶â€� æˆ�一个对象æ¯�个 lambda 处ç�†ä¸€ç§�ç±»å�‹ã€‚std::visit(overloaded{处ç�†intçš„lambda, 处ç�†doubleçš„lambda, 处ç�†stringçš„lambda}, v)å°±ä¼šæ ¹æ�®vçš„å®�é™…ç±»å�‹è°ƒç”¨å¯¹åº”çš„ lambda—— 这是å®�é™…å¼€å�‘䏿œ€å¸¸ç”¨çš„写法ä¸�用写结æ�„体直æ�¥ç”¨ lambda 组å�ˆä»£ç �更简æ´�。ä¸�过部分的大家呢这个会比较看ä¸�懂这里解释一下其å®�它是 C 里一个æ��å…¶å·§å¦™ä½†æ ¸å¿ƒç®€å�•的模æ�¿æŠ€å·§è¿™æ®µä»£ç �的目的是把多个ä¸�å�Œçš„ lambda或函数对象“å�ˆå¹¶â€� æˆ�一个对象让这个对象拥有所有 lambda çš„operator()é‡�è½½ç‰ˆæœ¬è¿™æ ·å°±èƒ½ç”¨å®ƒä½œä¸ºstd::visit的访问者匹é…�variantçš„ä¸�å�Œç±»å�‹ã€‚å…¶å®�就是上é�¢çš„æ–¹æ³•就是写法的差别而已templateclass... Ts struct overloaded : Ts... { using Ts::operator()...; };拆解æˆ� 3 个关键部分templateclass... Ts这是 C11 çš„å�˜å�‚模æ�¿Ts...表示 “任æ„�æ•°é‡�ã€�ä»»æ„�ç±»å�‹çš„æ¨¡æ�¿å�‚æ•°â€�æ¯”å¦‚ä¼ 3 个 lambdaTs就是这 3 个 lambda 的类å�‹ã€‚struct overloaded : Ts...overloaded结æ�„体公有继承了所有Ts里的类å�‹ä¹Ÿå°±æ˜¯ç»§æ‰¿äº†æ‰€æœ‰ä¼ 入的 lambda。lambda 本质是匿å��的函数对象æ¯�个 lambda 都有自己的operator()继承å��overloadedå°± “拥有â€� 了这些operator()。using Ts::operator()...;这是 C17 çš„åŒ…å±•å¼€è¯æ³•作用是 “把所有基类Tsçš„operator()都引入到overloaded的作用域ä¸â€�。â�Œ ä¸�åŠ è¿™è¡Œçš„é—®é¢˜C ä¸å�类继承多个基类的å�Œå��函数这里都是operator()时基类的函数会被 “éš�è—�â€�编译器ä¸�知é�“该调用哪个✅ åŠ è¿™è¡Œçš„ä½œç”¨æ˜¾å¼�把所有基类的operator()暴露出æ�¥è®©ç¼–è¯‘å™¨èƒ½æ ¹æ�®å�‚æ•°ç±»å�‹åŒ¹é…�对应的é‡�载版本。这æ‰�是é‡�点templateclass... Ts overloaded(Ts...) - overloadedTs...;这是 C17 的类模æ�¿æ�¨å¯¼æŒ‡å�—作用是当我们使用overloaded{lambda1, lambda2, lambda3}è¿™ç§�æ–¹å¼�创建对象时编译器能自动æ�¨å¯¼æ¨¡æ�¿å�‚æ•°Ts就是这 3 个 lambda 的类å�‹æ¯”如我们写的overloaded{[](int){}, [](double){}}编译器会æ�¨å¯¼Ts是 “处ç�† int çš„ lambda ç±»å�‹ 处ç�† double çš„ lambda ç±»å�‹â€�自动生æˆ�overloadedlambda1_type, lambda2_type的对象备注C20 起编译器能自动æ�¨å¯¼è¿™è¡Œå�¯ä»¥çœ�略但为了兼容通常会ä¿�留。overloaded是一个继承了多个类å�‹Ts...çš„å�˜å�‚模æ�¿ç»“æ�„ä½“å½“ä½ ç”¨overloaded{lambda1, lambda2}è¿™ç§� “è�šå�ˆåˆ�始化â€� 的方å¼�创建对象时C17 的编译器默认å�ªä¼š “ä»�结æ�„体的æˆ�员å�˜é‡�â€� æ�¨å¯¼æ¨¡æ�¿å�‚æ•°ä¸�会ä»� “基类列表Ts...â€� æ�¨å¯¼è€Œoverloaded结æ�„体本身没有任何æˆ�员å�˜é‡�å�ªæœ‰ç»§æ‰¿çš„基类所以编译器会直æ�¥æŠ¥é”™â€œæ— 法æ�¨å¯¼ overloaded 的模æ�¿å�‚æ•°â€�。第一æ¥å…ˆçœ‹ “有æˆ�员å�˜é‡�â€� çš„æ£å¸¸æƒ…况编译器能æ�¨å¯¼å�‡è®¾æˆ‘们写一个简å�•的模æ�¿ç»“æ�„体里é�¢æœ‰æˆ�员å�˜é‡�// 模æ�¿ç»“æ�„体有一个æˆ�员å�˜é‡�ç±»å�‹æ˜¯ T templateclass T struct MyStruct { T value; // æˆ�员å�˜é‡� }; int main() { // 用 {10} åˆ�始化编译器能æ�¨å¯¼ //
看到�员�� value 被赋值为 10int类� //
所以模æ�¿å�‚æ•° T int自动生æˆ� MyStructint MyStruct s{10}; return 0; }这个场景编译器能æ£å¸¸æ�¨å¯¼å› 为它能ä»�æˆ�员å�˜é‡�的赋值里找到模æ�¿å�‚数的匹é…�关系。第二æ¥å†�看 overloaded 的情况编译器æ�¨å¯¼å¤±è´¥å›�到我们的overloaded结æ�„体它的定义是templateclass... Ts struct overloaded : Ts... { // å�ªæœ‰åŸºç±» Ts...没有任何æˆ�员å�˜é‡� using Ts::operator()...; };å½“ä½ å†™overloaded{lambda1, lambda2}时问题就æ�¥äº†ç¼–译器的æ€�考过程C17“我è¦�æ�¨å¯¼ overloaded 的模æ�¿å�‚æ•° Ts...首先找它的æˆ�员å�˜é‡�…… 哦它没有æˆ�员å�˜é‡�â€�“那我该ä»�哪找 Ts... 的类å�‹åŸºç±»åˆ—表ä¸�行规则说我å�ªçœ‹æˆ�员å�˜é‡�ä¸�看基类â€�“完了找ä¸�到匹é…�的模æ�¿å�‚数报错â€�通俗比喻这就åƒ�ä½ å�»ä¹°å¥¶èŒ¶åº—员å�ªè®¤ “è�œå�•上的选项â€�æˆ�员å�˜é‡�ä¸�认 â€œèµ å“�â€�åŸºç±»ã€‚ä½ æŒ‡ç�€èµ å“�说 “我è¦�这个â€�店员会说 “我ä¸�知é�“这是什么没法下å�•â€�—— 编译器就是这个店员它å�ªçœ‹æˆ�员å�˜é‡�ä¸�认基类所以æ�¨å¯¼å¤±è´¥ã€‚ç¬¬ä¸‰æ¥æ�¨å¯¼æŒ‡å�—的作用给编译器 “开特例â€�我们写的æ�¨å¯¼æŒ‡å�—templateclass... Ts overloaded(Ts...) - overloadedTs...;æœ¬è´¨æ˜¯ç»™ç¼–è¯‘å™¨åŠ äº†ä¸€æ�¡ “特例规则â€�“当有人用overloaded{å�‚æ•°1, å�‚æ•°2,...}创建对象时ä¸�ç®¡ä½ æœ‰æ²¡æœ‰æˆ�员å�˜é‡�ç›´æ�¥æŠŠè¿™äº›å�‚数的类å�‹å½“æˆ�模æ�¿å�‚æ•° Ts...â€�åŠ ä¸Šè¿™æ�¡è§„则å��编译器å†�看到overloaded{lambda1, lambda2}“哦有æ�¨å¯¼æŒ‡å�—ä¸�用看æˆ�员å�˜é‡�了。â€�“å�‚æ•° 1 是 lambda1ç±»å�‹ L1å�‚æ•° 2 是 lambda2ç±»å�‹ L2。â€�“所以 Ts... L1, L2模æ�¿å�‚数就定了生æˆ� overloadedL1, L2â€�所以C17 编译器æ�¨å¯¼æ¨¡æ�¿å�‚æ•°æ—¶ “眼里å�ªæœ‰æˆ�员å�˜é‡�â€�而overloaded没有æˆ�员å�˜é‡�ã€�å�ªæœ‰ç»§æ‰¿çš„基类所以编译器猜ä¸�到模æ�¿å�‚æ•°æ�¨å¯¼æŒ‡å�—的作用就是 “绕开æˆ�员å�˜é‡�规则â€�ç›´æ�¥å‘Šè¯‰ç¼–译器用åˆ�始化å�‚数的类å�‹ä½œä¸ºæ¨¡æ�¿å�‚æ•°ã€‚ä½ å�¯ä»¥æŠŠè¿™ä¸ªè¿‡ç¨‹è®°æˆ�æ£å¸¸æƒ…况æˆ�员å�˜é‡�ç±»å�‹ → 模æ�¿å�‚数编译器会overloaded 情况åˆ�始化å�‚æ•°ç±»å�‹ → 模æ�¿å�‚数需è¦�æ�¨å¯¼æŒ‡å�—教编译器 综å�ˆæ¡ˆä¾‹ç®€åŒ–#include iostream #include list #include set #include string #include type_traits #include variant #include vector templateclass... Ts struct overloaded : Ts... { using Ts::operator()...; }; templateclass... Ts overloaded(Ts...) - overloadedTs...; // å®�ç�°ä¸€ä¸ªå“ˆå¸Œè¡¨æ¡¶å�¯ä»¥æ˜¯ä¸€ä¸ªé“¾è¡¨ä¹Ÿå�¯ä»¥æ˜¯ä¸€ä¸ªçº¢é»‘æ ‘ class HashTable { private: using Value std::variantstd::listint, std::setint; std::vectorValue _tables; public: HashTable(size_t len) : _tables(len) {} void insert(const int key) { size_t hash key % _tables.size(); // 扩容 if (std::holds_alternativestd::listint(_tables[hash])) { auto list std::getstd::listint(_tables[hash]); // å°�äº�则æ�’入到链表 if (list.size()
{ list.push_back(key); } else { // 大äº�则转æ�¢åˆ°çº¢é»‘æ ‘ std::setint s(list.begin(), list.end()); s.insert(key); _tables[hash] move(s); } } else { auto set std::getstd::setint(_tables[hash]); set.insert(key); } } bool find(const int key) { size_t hash key % _tables.size(); // 查找 auto findInList [key](std::listint list) - bool { return std::find(list.begin(), list.end(), key) ! list.end(); }; auto findInSet [key](std::setint set) - bool { return set.count(key); }; return std::visit(overloaded{ findInList, findInSet }, _tables[hash]); } }; int main() { HashTable ht(
; for (int i 0; i 10; i) { ht.insert(i *
; } std::cout ht.find(
std::endl; std::cout ht.find(
std::endl; return 0; }这个哈希表案例ä¸std::variantstd::listint, std::setint被用æ�¥å®šä¹‰å“ˆå¸Œæ¡¶çš„ç±»å�‹è®©æ¯�个桶既能å˜å‚¨é“¾è¡¨std::list也能å˜å‚¨çº¢é»‘æ ‘std::set—— æ�’å…¥å…ƒç´ æ—¶å…ˆé€šè¿‡std::holds_alternative判æ–当å‰�æ¡¶æ˜¯é“¾è¡¨è¿˜æ˜¯çº¢é»‘æ ‘è‹¥é“¾è¡¨å…ƒç´ æ•°è¶…è¿‡ 8 åˆ™è‡ªåŠ¨è½¬ä¸ºçº¢é»‘æ ‘æŸ¥æ‰¾å…ƒç´ æ—¶åˆ©ç”¨std::visit结å�ˆoverloadedæŠ€å·§æ ¹æ�®æ¡¶çš„å®�é™…ç±»å�‹é“¾è¡¨ / çº¢é»‘æ ‘è‡ªåŠ¨è°ƒç”¨å¯¹åº”çš„æŸ¥æ‰¾é€»è¾‘é“¾è¡¨ç”¨std::findã€�çº¢é»‘æ ‘ç”¨countstd::variantåœ¨è¿™é‡Œçš„æ ¸å¿ƒä»·å€¼æ˜¯ç”¨ç±»å�‹å®‰å…¨çš„æ–¹å¼�æ›¿ä»£ä¼ ç»Ÿ union既能ç�µæ´»å˜å‚¨ä¸¤ç§�ä¸�å�Œçš„容器类å�‹å�ˆèƒ½é€šè¿‡é…�套的std::holds_alternative/std::get/std::visit安全地处ç�†ä¸�å�Œç±»å�‹çš„逻辑é�¿å…�了手动管ç�†ç±»å�‹æ ‡è¯†çš„ç¹�ç��和出错é£�险å®�ç�°äº† “一个容器ä½�ç½®å˜å‚¨å¤šç§�ç±»å�‹ã€�且æ¯�ç§�ç±»å�‹æ‰§è¡Œä¸“å±�逻辑â€� 的需求。所以在这里std::variantæ‰¿è½½çš„æ ¸å¿ƒä»·å€¼å°±æ˜¯é€šè¿‡ “链表 / çº¢é»‘æ ‘çš„åˆ‡æ�¢è§„åˆ™å…ƒç´ æ•°â‰¥8â€� 这个数å¦é˜ˆå€¼å¹³è¡¡å“ˆå¸Œè¡¨çš„æ—¶é—´ / 空间消耗—— 链表std::list的优势是æ�’入快ã€�空间开销å°�但查找慢O (n)适å�ˆå…ƒç´ å°‘çš„åœºæ™¯çº¢é»‘æ ‘std::set的优势是查找快O (logn)但æ�’å…¥ / 空间开销大适å�ˆå…ƒç´ 多的场景。开å�‘时通过 “8 ä¸ªå…ƒç´ â€� 这个数å¦é˜ˆå€¼ä½œä¸ºåˆ‡æ�¢æ�¡ä»¶ç”¨std::variant让æ¯�ä¸ªå“ˆå¸Œæ¡¶æ ¹æ�®å…ƒç´ æ•°é‡�动æ€�切æ�¢å˜å‚¨ç±»å�‹å…ƒç´ 少的时候用链表çœ�空间ã€�å¿«æ�’å…¥å…ƒç´ å¤šçš„æ—¶å€™ç”¨çº¢é»‘æ ‘æ��查找效ç�‡æœ€ç»ˆå®�ç�° “ä½�å…ƒç´ é‡�æ—¶æ�§ç©ºé—´æ¶ˆè€—é«˜å…ƒç´ é‡�æ—¶æ�§æ—¶é—´æ¶ˆè€—â€� 的平衡而std::variant则是å®�ç�°è¿™ç§� “动æ€�ç±»å�‹åˆ‡æ�¢â€� 的类å�‹å®‰å…¨è½½ä½“é�¿å…�äº†ä¼ ç»Ÿæ–¹å¼�å¦‚æ‰‹åŠ¨æ ‡è®°ç±»å�‹ã€�强制类å�‹è½¬æ�¢çš„出错é£�险。简å�•å®�ç�°å�Ÿç�†std::visit本质是 “编译期生æˆ�ç±»å�‹åˆ†å�‘表 è¿�行时查表调用â€�把variant的类å�‹ç´¢å¼•æ˜ å°„åˆ°å¯¹åº”çš„å¤„ç�†å‡½æ•°ã€‚下é�¢æˆ‘用 “通俗å�Ÿç�† 简化å®�ç�°â€� 的方å¼�ç»™ä½ è®²é€�它的底层逻辑一ã€�std::visitæ ¸å¿ƒå®�ç�°å�Ÿç�†å¤§ç™½è¯�版编译期准备生æˆ� “类å�‹ - 函数â€� æ˜ å°„è¡¨ç¼–è¯‘å™¨ä¼šå…ˆåˆ†æ��variant的类å�‹åˆ—è¡¨æ¯”å¦‚æœ¬ä¾‹ä¸æ˜¯listintå’Œsetint以å�Šä½ ä¼ å…¥çš„è®¿é—®è€…overloaded组å�ˆçš„两个 lambda为æ¯�个类å�‹ç”Ÿæˆ�对应的 “处ç�†å‡½æ•°åœ°å�€â€�并按类å�‹ç´¢å¼•0 对应 listã€�1 对应 setæ•´ç�†æˆ�ä¸€å¼ â€œåˆ†å�‘表â€�。è¿�行时执行查表 调用程åº�è¿�行时std::visitå…ˆè�·å�–variant当å‰�å˜å‚¨ç±»å�‹çš„索引通过variant.index()ç„¶å��到 “分å�‘表â€� 里找到该索引对应的处ç�†å‡½æ•°æœ€å��把variant里的å®�é™…å€¼ä¼ ç»™è¿™ä¸ªå‡½æ•°æ‰§è¡Œ —— 整个过程就åƒ� â€œæ ¹æ�®ç±»å�‹ç¼–å�·æ‰¾å¯¹åº”的工具干活â€�。二ã€�简化版å®�ç�°å¸®ä½ ç�†è§£æ ¸å¿ƒé€»è¾‘我们用伪代ç �模拟std::visitçš„æ ¸å¿ƒé€»è¾‘ä½ ä¸€çœ‹å°±æ‡‚// 模拟 std::variant çš„æ ¸å¿ƒç»“æ�„ templateclass... Ts struct MyVariant { size_t index; // å˜å‚¨å½“å‰�ç±»å�‹çš„索引 // å˜å‚¨å®�际值的内å˜ç®€åŒ–版å®�际是对é½�的内å˜å�— alignas(Ts...) char data[max_sizeof(Ts...)]; // è�·å�–当å‰�ç±»å�‹ç´¢å¼• size_t get_index() const { return index; } // 按索引è�·å�–值的指针简化版 void* get_data() { return data; } }; // 模拟 std::visit çš„æ ¸å¿ƒå®�ç�° templateclass Visitor, class... Ts auto my_visit(Visitor visitor, MyVariantTs... var) { // 编译期生æˆ�ç±»å�‹ç´¢å¼• → 处ç�†å‡½æ•° çš„æ˜ å°„è¡¨ using FuncTable void* (*)[]; static FuncTable table { // 对æ¯�个类å�‹ Ts生æˆ�“把 var çš„å€¼ä¼ ç»™ visitorâ€�的函数 [](MyVariantTs... v) { return visitor(*static_castTs*(v.get_data())); }... }; // è¿�è¡Œæ—¶æ ¹æ�®ç´¢å¼•查表调用对应函数 size_t idx var.get_index(); return table[idx](var); }这个简化版里编译期table数组会被编译器生æˆ�æ¯�ä¸ªå…ƒç´ å¯¹åº”ä¸€ä¸ªç±»å�‹çš„处ç�†å‡½æ•°è¿�行时å�ªéœ€è¦�æ ¹æ�®indexå�–æ•°ç»„å…ƒç´ è°ƒç”¨å‡½æ•°å�³å�¯æ²¡æœ‰å¤šä½™çš„if-else分支。三ã€�结å�ˆå“ˆå¸Œè¡¨æ¡ˆä¾‹çš„具体执行æµ�ç¨‹åœ¨ä½ çš„å“ˆå¸Œè¡¨find函数ä¸return std::visit(overloaded{findInList, findInSet}, _tables[hash]);编译期编译器生æˆ�ä¸€å¼ è¡¨ç´¢å¼• 0 对应findInList处ç�† listã€�索引 1 对应findInSet处ç�† setè¿�行时先è�·å�–_tables[hash]的索引0 或 1若索引是 0 → 调用findInList把variant里的 list ä¼ ç»™å®ƒè‹¥ç´¢å¼•æ˜¯ 1 → 调用findInSet把variant里的 set ä¼ ç»™å®ƒæœ€ç»ˆè¿”å›�查找结æ�œã€‚总结std::visitçš„æ ¸å¿ƒæ˜¯ç¼–è¯‘æœŸç”Ÿæˆ�分å�‘表è¿�行时快速查表调用比手动写if-else get_if更高效它的å�Ÿç�†æœ¬è´¨æ˜¯ “把类å�‹åˆ¤æ–ä»�è¿�行时的分支æ��å‰�到编译期的表生æˆ�â€�æ—¢ä¿�è¯�ç±»å�‹å®‰å…¨å�ˆä¸�æ�Ÿå¤±æ€§èƒ½åœ¨å“ˆå¸Œè¡¨æ¡ˆä¾‹ä¸å®ƒçš„作用就是 â€œæ ¹æ�® variant çš„å®�é™…ç±»å�‹list/set自动调用对应的查找函数â€�ä¸�用手动写分支判æ–。总结std::variant作为 C17 的类å�‹å®‰å…¨è�”å�ˆä½“æ ¸å¿ƒå�ªå…�è®¸å˜æ”¾å�¯æ��æ�„ã€�å�¯ç§»åЍ / æ‹·è´�ã€�å�¯å®�例化的é��引用值类å�‹ç»�对ä¸�èƒ½å˜æ”¾å¼•用类å�‹éœ€ç”¨std::reference_wrapper包装ã€�voidç±»å�‹ã€�ä¸�完整类å�‹æœªå®šä¹‰çš„结æ�„体ã€�抽象类å�«çº¯è™šå‡½æ•°å�Œæ—¶ä¸�å»ºè®®å˜æ”¾é‡�å¤�ç±»å�‹å¦‚variantint, int会导致std::get编译报错ã€�æ— å�ˆæ³•移动 / æ‹·è´�è¯ä¹‰çš„å¤�æ�‚ç±»å�‹æ˜“资æº�泄æ¼�ã€�超大内å˜ç±»å�‹å¾’å¢�variant内å˜å¼€é”€è€Œ C17 ä¸è¿˜è¦�求其第一个类å�‹å¿…须有默认æ�„é€ å‡½æ•°C20 放宽æ¤é™�åˆ¶ä½ çš„å“ˆå¸Œè¡¨æ¡ˆä¾‹ä¸listintå’Œsetintå› æ»¡è¶³ “å�¯é»˜è®¤æ�„é€ ã€�å�¯ç§»åЍã€�尺寸适ä¸â€� çš„è¦�求是variant的典å�‹å�ˆç�†ç”¨æ³•。所以std::variantç¦�å˜å¼•用ã€�voidã€�ä¸�完整类å�‹ã€�抽象类ä¸�建议å˜é‡�å¤�ç±»å�‹ã€�æ— å�ˆæ³•移动 / æ‹·è´�的类å�‹ã€�超大类å�‹æ ¸å¿ƒè¦�æ±‚å˜æ”¾ç±»å�‹éœ€æ˜¯å�¯æ��æ�„ã€�å�¯ç§»åЍ / æ‹·è´�ã€�å�¯å®�例化的值类å�‹ã€‚
樱花ppt网站点击进入-樱花ppt网站点击进入应用