C++ standard library 15 साल से खुद को पीछे खींचती रही है, और उसके सबूत सार्वजनिक हैं
(hftuniversity.com)- C++ standard library ने C++11 के बाद खराब डिज़ाइनों को औपचारिक रूप से deprecate करने या नए replacement features के साथ उन्हें छोड़ देने का काम बार-बार किया है, और अब संरचना ऐसी हो गई है जिसमें developers को यह जानना पड़ता है कि “किस layer का इस्तेमाल नहीं करना चाहिए”
- औपचारिक रूप से वापस ली गई layer में
std::auto_ptr, dynamic exception specifications, C++11 garbage collection interface,std::aligned_storageजैसे items शामिल हैं जिन पर deprecation/removal papers लगे;std::functionभीstd::move_only_function,std::copyable_function,std::function_refसे घिरे 15 साल लंबे replacement flow में रखा गया है - अनौपचारिक रूप से टाली जाने वाली layer में धीमा
std::regex, destructor में wait करके deadlock trap बनाने वालाstd::async,<iostream>,std::list,std::deque,std::vector<bool>जैसे features आते हैं, जो standard में तो बचे हैं लेकिन production code अक्सर इन्हें bypass करता है - default containers की समस्या
std::unordered_map,std::map,std::listमें सबसे साफ दिखती है; एक ही workload benchmark में C++ naive implementation का P99 302,653 cycles और Rust naive implementation का 5,177 cycles रहा, यानी 58x का अंतर - ABI stability का चुनाव वह मुख्य फर्क है जिसकी वजह से दूसरी भाषाएँ delete, editions, या major version transitions के ज़रिए गलतियों को कम कर लेती हैं, जबकि C++ अपने खराब defaults को
std::के भीतर लगभग स्थायी रूप से बचाकर रखता है
शुरुआत: std::function को “legacy” मानना
- Sandor Dargo की
std::copyable_functionquick reference tablestd::functionको “Legacy. Avoid in new code.” के रूप में वर्गीकृत करती है std::functionC++11 में आया था, और उसका latest replacement wrapperstd::copyable_functionC++26 में आता है; नए feature की सिफारिश का भाव “जब copyable callable object चाहिए तो इसे इस्तेमाल करो” से ज़्यादा “पुराने वाले का इस्तेमाल मत करो” के करीब हैstd::functionकाconst operator()एक const-correctness defect रखता है, क्योंकि यह non-const callable object को call कर सकता है, और ABI तोड़े बिना इसे ठीक नहीं किया जा सकता- इसी defect के जवाब में
std::move_only_functionC++23 के P0288R9,std::copyable_functionC++26 के P2548R6, औरstd::function_refC++26 के P0792R14 वाले flow में रखे गए हैं
वे standard features जिन्हें औपचारिक रूप से वापस लिया गया
std::auto_ptrकी copy-move semantics generic code और standard containers को तोड़ देती थीं; इसे C++11 में deprecated किया गया और C++17 में N4190 से हटा दिया गया; इसी paper ने C++98<functional>adapters औरstd::random_shuffleको भी हटायाstd::random_shufflestd::randऔर global state पर निर्भर था, इसलिए इसेstd::shuffleसे replace किया गया- dynamic exception specification
throw(X, Y)को C++11 में deprecated किया गया, C++17 में P0003R5 से हटाया गया, औरthrow()alias को C++20 के P1152 से हटा दिया गया std::iteratorको C++17 में P0174R2 से deprecated किया गया, और C++26 removal P3365R1 में आगे बढ़ रही है; replacement तरीका पाँच typedefs को सीधे define करना हैstd::aligned_storageऔरstd::aligned_unionC++11 में आए, फिर C++23 के P1413R3 से deprecated हुए; समस्याओं मेंtypename ::typeboilerplate,reinterpret_cast,Len == 0पर undefined behavior, और constexpr की अनुपस्थिति शामिल थींstd::not1,std::not2,unary_negate,binary_negateC++17 में deprecated हुए, C++20 में हटाए गए, और P0005 केstd::not_fnसे replace हुए- C++11 garbage collection interface की
std::declare_reachablefamily को C++23 के P2186R2 से हटा दिया गया, क्योंकि major implementations ने कोई वास्तविक garbage collector दिया ही नहीं - Concepts TS, Modules TS, Coroutines TS, Reflection TS, Executors TS, Networking TS भी merge होने से पहले redesign, replacement, या delay से गुज़रे; Reflection P2996 की दिशा में बदला और Executors P2300 के sender/receiver flow में बदल गए
वे features जो standard में हैं, लेकिन ज़मीन पर लोग उनसे बचते हैं
std::regexC++11 में आया, लेकिन P1844R1 ने committee record में यह दर्ज किया कि इसका performance “दूसरे उपलब्ध समाधानों की तुलना में बहुत खराब” है; replacement flow में CTRE और P1433R0 आते हैं, और standard के बाहर Boost.Regex, RE2, PCRE2 हैंstd::asyncमें returned future का destructor asynchronous task के पूरा होने तक block करता है, और N3679 ने इससे बनने वाले deadlock trap को दर्ज किया<iostream>धीमा है, locale से बँधा है, formatting में thread-safe नहीं है, और इसके error messages बदनाम हैं; फिर भी C++20 के P0645std::formatऔर C++23 के P2093std::print·std::printlnआने के बाद भी इसे deprecated नहीं किया गयाstd::listवह item है जिसके बारे में Bjarne Stroustrup ने 2012 GoingNative keynote में दिखाया कि middle insertion workload में भीstd::vectorजीत सकता है; बाद की post Are lists evil? का जवाब भी लगभग “हाँ” के करीब हैstd::dequeMicrosoft STL की public issue microsoft/STL#147 में ऐसा item दर्ज है जहाँ standard द्वारा तय block size बहुत छोटा है, और अगली ABI break पर बड़े performance redesign की ज़रूरत होगीstd::valarray1998 में एक numeric container के रूप में आया था, लेकिन expression template optimization साकार नहीं हुई, और cppreference के अनुसार implementations में सामान्य containers से बढ़कर कोई खास codepath नज़र नहीं आताstd::vector<bool>पर Howard Hinnant की Onvector<bool>एक प्रतिनिधि analysis है; bit-packed storage अपने आप में उपयोगी है, लेकिनstd::vectorspecialization की तरह इसका नाम generic code मेंT = boolहोने पर गलत behavior की trap बनाता हैvolatileको C++20 के P1152R4 से compound operations और parameter/return positions में deprecated किया गया था, फिर C++23 के P2327R1 से कुछ rollback हुआ, और C++26 के P2866R0 में आगे rollback तय है
वे default containers जिन्हें ABI के कारण ठीक नहीं किया जा सकता
std::unordered_mapमें C++11 spec के bucket और iterator stability rules open addressing को लगभग असंभव बना देते हैं; Google SwissTable design कोstd::unordered_mapपर लगभग 3x performance advantage दिखाने वाला बताया गया है- Folly F14, Boost
unordered_flat_map,ankerl::unordered_denseजैसी alternatives भी इसी दिशा की हैं; RustHashMapstandard library default के रूप में hashbrown SwissTable port का उपयोग करता है std::mapऔरstd::setnode-based red-black tree हैं, इसलिए हर node पर heap allocation चाहिए और हर traversal पर pointer chasing होती है; Abseilbtree_mapऔर RustBTreeMapB-tree आधारित होने के कारण इस समस्या से बचते हैं- C++23 ने P0429R9 से
std::flat_mapऔरstd::flat_setजोड़े, लेकिनstd::unordered_map,std::map,std::listकी मूल default design नहीं बदल सका - multi-symbol order book benchmark में एक ही workload, एक ही seed, और एक ही isolated core पर C++ के
std::unordered_map+std::map+std::listकी तुलना Rust केHashMap+BTreeMap+VecDequeसे की गई
| Implementation | P99 cycles |
|---|---|
C++ naive (unordered_map + map + list) |
302,653 |
C++ step 1 (flat_hash_map + map + deque) |
9,951 |
C++ step 2 (flat_hash_map + btree_map + deque) |
9,114 |
C++ step 3 (flat_hash_map + btree_map + vector) |
4,268 |
Rust naive (HashMap + BTreeMap + VecDeque) |
5,177 |
- सिर्फ
std::listसेstd::vectorपर जाने से लगभग 70x सुधार दिखा,std::unordered_mapसेflat_hash_mapपर जाने से 3–5x, औरstd::mapसेbtree_mapपर जाने से 1.09x का, यानी noise range के भीतर रहने वाला असर दिखा - तुलना का मकसद यह कहना नहीं है कि Rust भाषा खुद C++ से 58x तेज़ है, बल्कि यह कि Rust standard library ने सही defaults चुने, जबकि C++ standard library ABI की वजह से अपने तीन defaults नहीं बदल सकती
Vasa समस्या और features का जमाव
- Bjarne Stroustrup के 2018 WG21 document P0977R0 “Remember the Vasa!” में 1628 के Swedish warship Vasa के डूबने की उपमा देते हुए कहा गया कि committee में “लगभग 150 cooks” हैं, और individual features का पूरे system पर असर पर्याप्त रूप से नहीं देखा जाता
std::simdको std::simd Is a Solution to the Wrong Problem में उसी pattern के प्रतिनिधि item के रूप में लिया गया है; Matthias Kretz ने इसे Vc library से शुरू कर P0214, Parallelism TS 2, और P1928 से होते हुए C++26 तक पहुँचाया- जब
std::simdstandard में आया, तब standard के बाहर Google Highway, ISPC, EVE, xsimd, SIMDe मौजूद थे, और GCC तथा Clang के auto-vectorizers भी बेहतर हो चुके थे; दावे के अनुसार-O3 -march=nativescalar loop कभी-कभीstd::simdसे बेहतर निकला std::simdमें समस्याएँ यह बताई गईं कि equivalent scalar code की तुलना में compile time 10x धीमा है, यह उस auto-vectorizer से भी धीमा पड़ सकता है जिसे replace करना था, और यह ARM SVE के scalable-width vectors तथा runtime dispatch को व्यक्त नहीं कर पाता- libstdc++, libc++, और MSVC STL — इन तीनों implementations को single-digit size की engineering teams maintain करती हैं; हर नया standard feature test matrix, conformance bugs, feature interaction, और अगले maintainer के लिए bug tracker entries बढ़ाता है
std::regex15 साल से ज्ञात समस्याओं के साथ मौजूद है,std::dequeपर redesign-needed issue है, और C++20 modules को standardization के 6 साल बाद भी तीनों implementations में साफ़-सुथरे तरीके से काम न करने वाली स्थिति के रूप में बताया गया है- आधुनिक C++ standard की वास्तविक operational knowledge कुछ गिने-चुने full-time experts में सिमट जाती है, जिन्हें गलत layers के समयक्रम, third-party workarounds, तीन standard library implementations के फर्क, और theory बनाम practice का अंतर समझना पड़ता है
दूसरी भाषाओं से फर्क: गलती नहीं, preservation rate
- Python ने PEP 594 से 20 से अधिक standard library modules हटाए, PEP 632 से
distutilsको Python 3.12 में हटाया, और PEP 387 खतरनाक या टूटी हुई features के लिए deprecation cycle छोटा करने का अधिकार देती है - Java ने Applet API को Java 9 में deprecated किया, Java 17 में removal path पर रखा, और JEP 504 के तहत 8 साल की प्रक्रिया में वास्तविक removal तक पहुँचाया; Nashorn को JEP 372 से Java 15 में हटा दिया गया
- Java SecurityManager को JEP 411 से deprecation-for-removal दिया गया और JEP 486 से स्थायी रूप से disable किया गया; JEP 398 Applet API removal path को कवर करता है
- Rust 2015, 2018, 2021, 2024 editions को
Cargo.tomlमें crate-by-crate opt-in के रूप में चुनने देता है;mem::uninitializedकोMaybeUninit,std::error::Error::descriptionकोsource, औरtry!macro को?operator ने replace किया - C# ने .NET Framework से .NET Core की ओर जाते समय BinaryFormatter, AppDomains, Remoting, Code Access Security, WCF server, WebForms जैसी चीज़ें छोड़ने वाले major version transition को स्वीकार किया
- JavaScript web compatibility constraints के कारण बहुत कम remove करता है, लेकिन cancelable promises Stage 1 पर वापस ले लिए गए, SIMD.js को WebAssembly SIMD की दिशा में छोड़ दिया गया, और Go ने Go 1 compatibility promise के कारण
io/ioutilको सिर्फ deprecated छोड़ने का रास्ता चुना - C++ का फर्क यह नहीं कि उससे गलतियाँ हुईं या नहीं, बल्कि यह है कि
std::regex,std::unordered_map,std::vector<bool>,std::valarray,std::functionका const-correctness defect जैसी चीज़ों को वह लगभग हटा ही नहीं पाता
ABI stability कैसे स्थायी संरक्षण बनाती है
- P1863R1 “ABI - Now or Never” ऐसा flow था जिसने पूछा कि C++23 में ABI break स्वीकार की जाए या स्थायी ABI stability चुनी जाए; committee ने व्यवहार में स्थायी ABI stability ही चुनी
- इसी चुनाव की वजह से
std::regexको ठीक करना,std::unordered_mapको open-addressing में बदलना, औरstd::list,std::map,std::dequeकी संरचना बदलना कठिन हो गया - C++ standard library ABI को dynamic linker enforce करता है; एक libstdc++ release से compile हुआ object दूसरे release के objects से link हो सके, इसलिए
std::stringlayout औरstd::regex_traitsconfiguration जैसी details distributed binaries में जड़ हो जाती हैं - यह constraint libstdc++ ABI policy और Itanium C++ ABI जैसे documents में concretely दर्ज है
- Python user
python==3.12चुन सकता है, Rust userCargo.tomledition, Java user JDK version, और C# usernet6.0याnet8.0TFM चुन सकता है, लेकिन C++ मेंstd::के लिए कोईCargo.tomlनहीं है -std=c++26यह चुनता है कि कौन-से headers और language rules इस्तेमाल होंगे, लेकिन यह कोई अलगstd::stringया redesign किया गयाstd::unordered_mapउपलब्ध नहीं कराता- इसलिए 2026 में production में भेजी जा रही C++ standard library, 1998 के बाद committee द्वारा स्वीकार किए गए खराब defaults को design और enforcement — दोनों के कारण — अब भी साथ लेकर चलती है
- tier-one trading firm, search engine, और browser जैसी आधुनिक C++ codebases Boost, Abseil, Folly, EASTL, Chromium
//base, custom containers, custom allocators, CTRE, Outcome, और coroutine libraries जैसी non-standard libraries पर भारी निर्भरता रखती हैं
2 टिप्पणियां
मूल लेख काफ़ी भारी-भरकम है, लेकिन अंत तक पढ़ते-पढ़ते इसमें rust के प्रबल समर्थक जैसा एहसास काफ़ी आने लगा।
फिर भी, मुझे बहुत-सी ऐसी जानकारियाँ मिलीं जो पहले नहीं पता थीं। अच्छे लेख के लिए धन्यवाद।
Lobste.rs की राय
अगर Rust ecosystem में भी ऐसे ही churn को याद करने की कोशिश करूँ, तो बड़े मामले शायद गिनती के ही थे
Leakpocalypse के समय यह निष्कर्ष निकला था कि
Dropdestructor पर इस बात के लिए हमेशा भरोसा नहीं किया जा सकता कि वह safety invariants बनाए रखने हेतु ज़रूर चलेगा, और वास्तविक API बदलाव लगभग नहीं के बराबर थे; बसstd::thread::scopedको हटाया गया था। बाद में ऐसा विकल्प आ गया जो वही काम sound तरीके से करता हैstd::mem::uninitializedको deprecated कर दिया गया, और अब उसे unsound माना जाता है। मौजूदाRangetypes को अपेक्षाकृत छोटे API मुद्दे ठीक करने के लिए लगभग वैसी ही नई types से धीरे-धीरे बदला जाना है।std::error::Error::descriptionको deprecated किया गया क्योंकि ज़्यादातर error types string स्टोर नहीं करना चाहतीं, और उसका सीधा विकल्पDisplayimplementation हैयह सोचकर काफ़ी हैरानी होती है कि
std11 साल तक स्थिर रहा, और बाकीstdअब भी मौजूद है, काम करता है, और उसका 98% हिस्सा आज भी idiomatic Rust माना जाता है। इसके उलट C++ standard library feature जोड़ने में कुछ ज़्यादा ही जल्दबाज़ लगती है, और किसी भी स्थिति में deprecate करने को लेकर हैरतअंगेज़ रूप से conservative और ख़तरनाक स्थिति में दिखती हैIteratortrait में अपने ही contents को borrow करने वाली समस्या भी याद आती है। Rust की चर्चाओं में यह बार-बार आने वाली पुरानी दिक्कत है: “मैं यह क्यों नहीं इस्तेमाल कर सकता और workaround क्यों चाहिए?”इसी तरह
f32औरf64काCmpimplement न करना और उसकी जगहf32::total_cmpmethod देना भी नई engineers को अक्सर टकराने वाली झुंझलाहट है, तो फिर लंबी साँस लेकर उसका background समझाना पड़ता हैpanic formatting machinery भी कुछ खास अच्छी नहीं है, और इस पर कई blog posts हैं कि default panic handler formatting का उपयोग करता है, उसे बंद करना मुश्किल है, और वह executable size का अच्छा-खासा हिस्सा खा जाता है
मेरी निजी राय में standard library की पुरानी design C++ की लोकप्रियता और usability को काफ़ी कम कर देती है
जिन कई समस्याओं का दोष लोग भाषा पर डालते हैं, उनका निशाना दरअसल standard library होना चाहिए
उदाहरण के लिए, “C++ compile होने में धीमा है” यह बात सटीक नहीं है। C++ features इस्तेमाल करना अपने-आप में मूलतः धीमा नहीं है; उसे धीमा बनाती है standard library, जिसमें भारी header bloat और dependencies हैं, और साधारण abstractions के लिए भी templates का अत्यधिक इस्तेमाल है
“C++ सुरक्षित नहीं है” यह भी कुछ हद तक सही है, लेकिन standard library की design उसे और बदतर बना देती है। Rust API design में इस्तेमाल होने वाले अधिक सुरक्षित patterns को नई standard library पर लागू न कर पाने की कोई वजह नहीं है। हाँ, C++ की सबसे बड़ी ताकतों में से एक backward compatibility है, इसलिए यह बहुत जटिल समस्या है
vec[idx]को out-of-bounds access/undefined behavior की जगह exception throw करने या abort करने वाला बनाया जा सकता है। लेकिन language differences की वजह से कई मामलों में C++ में सुरक्षित API बनाना कहीं ज़्यादा कठिन हैRust में मूल रूप से destructive move है, लेकिन C++ में नहीं। इसलिए smart pointer APIs या तो unsafe होंगी, या कम-से-कम चौंकाने वाली और crash-y बनेंगी। उदाहरण के लिए, moved-from smart pointer को access करने पर program abort कर देना
Rust में lifetime annotations हैं, लेकिन C++ में नहीं। इसलिए Rust iterator API design में iterator invalidation जैसी चीज़ों को रोक सकता है, जबकि C++ में यह व्यावहारिक रूप से बहुत कठिन है। Rust में pattern matching भी है, इसलिए
Optionजैसी APIs “पहले जाँचो, फिर तुरंत उपयोग करो” वाला तरीका ergonomically दे सकती हैं। C++ ऐसाstd::optionversion दे सकता है जिसमें empty value access करने पर UB न हो, लेकिन वह आज के C++ या Rust दोनों से कहीं ज़्यादा झंझट वाला होगा। Rust का?operator भी यहाँ बहुत मदद करता हैमुझे पता है कि
std::variantजैसी चीज़ों में overload set के ज़रिए pattern matching जैसा कुछ C++ में जोड़ा जा सकता है, लेकिन वह कहीं ज़्यादा awkward है और उसमें गलती की संभावना भी अधिक हैअगर बस एक आधुनिक library मिल जाए जिसमें strings और arrays की library, कुछ generic containers, और allocator के लिए native support हो, तो C काफ़ी ज़्यादा ergonomic और इस्तेमाल में आसान हो सकती है। बेशक, भाषा की कुछ कमियाँ सिर्फ library बदल देने से गायब नहीं होंगी, लेकिन फिर भी इससे बहुत दूर तक जाया जा सकता है
आधुनिक C codebases को देखें तो वे allocator, strings, vector, hash table, और filesystem operations के लिए custom libraries का बहुत व्यापक उपयोग करते हैं, और अगर आपको C या manual resource management का अनुभव है तो उस दिशा में जाना इतना मुश्किल नहीं है
slice<T, N>का implementation इस्तेमाल होता है, जो “ठीक N bytes को point करने वाला pointer” या “मनचाही संख्या के bytes को point करने वाला pointer” व्यक्त कर सकता हैइसमें
head(n),tail(n),slice(start, end), और index operator हैं, और ये सभी bounds checking करते हैंऐसी abstractions के साथ काम करना सचमुच आनंददायक है, लेकिन आधुनिक और कुछ हद तक सुरक्षित भाषा जैसी चीज़ पाने के लिए आपको असल में Rust और Zig standard libraries को C++ में port करना पड़ता है। फिर भी, आख़िरकार यह मेहनत के लायक है
अगर आप ऐसा लेख लिखने वाले हैं, तो कृपया उसे खुद लिखें। सूची आपने शायद खुद बनाई हो, लेकिन उसे LLM में डालकर परिणाम को वेबपेज पर इंसानों के पढ़ने के लिए उछाल देना बेहद असभ्य है। अगर मैं “काम करने वाला engineer” “पहले दिन” से “feature X” से बचना सीखता है—ऐसा वाक्य एक बार और देखूँ, तो शायद पागल हो जाऊँगा
शर्म की बात यह है कि यहाँ कहने को सच में बहुत कुछ है, लेकिन असल में कुछ कहा ही नहीं जा रहा। इस लेख को बनाने की कोई वजह रही होगी, तो वह वजह बतानी चाहिए थी। C++ के किस हिस्से ने आपको नाराज़ किया, किन features ने आपको उलझाया—यह बताया जाना चाहिए था। ये features सिर्फ़ objective design failures की वजह से बुरे नहीं हैं, बल्कि इसलिए भी कि इनका हम पर क्या असर पड़ता है
क्या आपने
std::iteratorइस्तेमाल करके Slack पर डाँट खाई थी, याreinterpret_cast16 अक्षरों का होने से line formatting थोड़ा बिगड़ जाएगी इस डर से cast न करने का फैसला किया था? ऐसी बातें अगर Lobsters पर आतीं तो बेहतर होता। अगर ऐसी कहानियाँ नहीं हैं, तो उन्हें ज़बरदस्ती मत गढ़िए, और GPU से matrix multiplication करवाकर वही वाक्य 10 बार मत लिखवाइए। जिन हिस्सों पर टिप्पणी करनी है, बस उन पर annotation डालिए, और बाकी को table और bullet point में लिख दीजिएमैं C++ को 20 साल से इस्तेमाल कर रहा हूँ और अभी भी करता हूँ, लेकिन इस लेख की बहुत-सी बातों से सहमत हूँ। आजकल Rust इस्तेमाल करते समय जो चीज़ सच में बहुत अच्छी लगती है, वह memory safety से भी ज़्यादा इसकी बेहतरीन standard library और package ecosystem है
इसका एक प्रमुख उदाहरण ranges library है। इसे standardize हुए 6 साल हो चुके हैं, फिर भी प्रमुख standard libraries अभी तक इसे पूरी तरह implement नहीं कर पाई हैं, और implement होने पर भी combinators बस कुछ ही हैं। Rust में इसका समकक्ष
Iteratormethods हैं, जो 76 हैं, औरcargo addएक बार चलाने परitertoolstrait के ज़रिए 130 और मिल जाते हैंएक और चीज़ जिसकी सच में बहुत कमी महसूस होती है, वह है pattern matching। इससे
std::variantजैसे union types को ergonomic तरीके से बनाया जा सकता है। इसका proposal चर्चा में है, लेकिन C++26 में भी अभी तक शामिल नहीं हुआ, और यह अफसोस की बात है। दूसरी ओर contracts और executors आ रहे हैं, जबकि सच कहूँ तो मैंने अपने आसपास किसी को इन्हें माँगते नहीं देखाआम तौर पर मेरा मानदंड यह है। अगर कोई सुविधा वांछनीय use cases को support करती है और उसे standard library के रूप में व्यक्त नहीं किया जा सकता, तो उसे language में जाना चाहिए। जहाँ तक संभव हो, वांछित सुविधा को ऐसे न्यूनतम स्वतंत्र हिस्सों में बाँटना चाहिए जिन्हें दूसरे उद्देश्यों के लिए भी इस्तेमाल किया जा सके
जो सुविधाएँ लगभग हर codebase में इस्तेमाल होती हैं, उन्हें standard library में होना चाहिए। अगर कोई type libraries के बीच interface के रूप में आम तौर पर इस्तेमाल होता है, तो वह standard library में होना चाहिए। हम यह नहीं चाहेंगे कि हर library अपना अलग tuple type या string define करे। C++ में पहले मामले में C++11 से पहले व्यवहारिक रूप से यही स्थिति थी, और दूसरे मामले में
std::stringएक disaster होने के कारण आज भी कुछ हद तक ऐसा है। यह interface types पर भी लागू होता है, और C++ आजकल इसे ज़्यादातर concepts से संभालता हैबाकी चीज़ें reusable modular libraries में जानी चाहिए। Rust स्थिर और blessed external libraries के संग्रह को बनाए रखने में काफ़ी अच्छा है, इसलिए “Rust में लिखे हर game को इस data structure की ज़रूरत है, तो इसे standard library में डाल देते हैं” जैसा दबाव बहुत कम है। game developers ज़रूरत के हिसाब से crate ले आते हैं। C++ ने “ऐसे अच्छे packages की सिफारिश, जो बहुत-से लेकिन बहुमत नहीं रखने वाले लोगों की समस्याओं के काम आएँ” इस विचार को कभी ठीक से अपनाया ही नहीं
जो बात चिंताजनक लगती है, वह यह है कि अभी जो चीज़ें जोड़ी जा रही हैं उनमें से कौन-सी आगे चलकर फिर वापस लेनी पड़ेगी। Contracts अभी-अभी C++26 में शामिल हुए हैं, और उन पर पहले से ही गंभीर design flaws की ओर इशारा किया जा रहा है
मैं सामान्य रूप से “committee design” की निंदा नहीं करना चाहता। मेरा मानना है कि ऐसे निकाय महत्वपूर्ण उद्देश्य पूरे करते हैं और उनकी अपनी विशिष्ट ताकतें होती हैं। बस उनकी ताकत बिल्कुल नई सुविधाओं को एकदम खाली मैदान से design करने में नहीं है
WG21 और WG14 सच में वहाँ चमकते हैं जहाँ design space का कुछ हद तक अन्वेषण हो चुका हो, और जहाँ संभव हो, कई मौजूदा implementations वाली किसी सुविधा को उठाकर उसे ऐसा standard feature बनाया जाए जिसे ज़्यादातर users और implementers स्वीकार कर सकें।
std::embedइसका एक उदाहरण हैइसके उलट, लेख में बताए गए GC extension,
std::memory_order_consume, और C++20 modules जैसी चीज़ों को अगर किसी के ठीक से implement कर पाने से पहले ही standardize कर दिया जाए, तो हालात अक्सर बहुत खराब हो जाते हैंपहले मुझे यह जानकर काफ़ी झटका लगा था कि C++ अपनी standard library का versioning नहीं करता। यह लेख ठीक इसी बात पर उंगली रखेगा, यह उम्मीद नहीं थी
यह बात भी दिलचस्प लगी कि Go को forward compatibility के मामले में कुछ हद तक इसी तरह conservative बताया गया है। लेकिन Go नई features जोड़ने में भी उतना ही conservative है, इसलिए लगता है कि उसने C++ की ज़्यादातर समस्याओं से बचाव कर लिया। Stable ABI न होना भी शायद मददगार रहा होगा
जितनी लोकप्रिय libraries मैं जानता हूँ, उनमें C++ ABI को explicitly expose करने वाली सिर्फ libcamera है, और यह काफ़ी झंझटभरा है। मेरे अनुभव में C++ libraries भी आम तौर पर symbols को C ABI के रूप में export करती हैं, और इससे दूसरी भाषाओं के साथ interoperability भी आसान हो जाती है। हो सकता है मैंने यहाँ कुछ मिस किया हो
और Clang और MSVC के बीच ABI compatibility में कुछ quirks नहीं हैं क्या? मुझे याद है Conan compiler mixing को explicitly discourage करता था या मना करता था, इसलिए समझ नहीं आता कि C++ committee ABI stability बचाए रखने की इतनी कोशिश क्यों करती है
यहाँ दो चीज़ें हैं जो काफ़ी क़रीब से जुड़ी हुई हैं: standard library specification और implementation। Specification पूरी language+library combination के लिए होती है, और implementation आम तौर पर specification के कम-से-कम एक या उससे अधिक versions को support करने की कोशिश करता है
C++ interface expose करने वाली libraries बहुत हैं, और उनमें Qt जैसी बहुत बड़ी libraries भी शामिल हैं
असली समस्या यह है कि C++ abstract machine linking process को define नहीं करती। इसलिए यह define ही नहीं किया जा सकता कि dynamic libraries कैसे काम करेंगी। UNIX systems पर C++ dynamic linking, C model का अनुसरण करता है। यानी dynamic linking होने का दिखावा, लेकिन असली बात loader की समस्या बताकर टाल देना। इसी वजह से copy relocation जैसी भयानक चीज़ें पैदा होती हैं। Windows के पास shared library क्या होती है, इसका कहीं ज़्यादा principled concept है, लेकिन इसी कारण UNIX C++ libraries के कुछ idioms Windows पर काम नहीं कर पाते
Shared libraries, C++ templates जैसी सुविधाओं के साथ बड़ी समस्या पैदा करती हैं। अगर templates को user types के साथ instantiate करना है, तो compiler compilation-unit boundaries के पार नहीं देख सकता, इसलिए पूरी definition header में होनी चाहिए। Shared libraries में वही code कई जगह instantiate होता है। अगर program और library दोनों एक ही parameters के साथ एक ही template को instantiate करें, तो दोनों के पास उसकी copy होगी, और फिर linker और loader को यह सुनिश्चित करना होगा कि अंतिम loaded program में सिर्फ एक ही इस्तेमाल हो
Swift से तुलना करें तो Swift साफ़ कहता है कि “shared libraries मौजूद हैं, और भाषा-स्तर की संरचनाएँ उन्हें व्यक्त करती हैं।” अगर आप shared library boundaries के पार generics expose करना चाहते हैं, तो कर सकते हैं, लेकिन सभी external callers के लिए वे dynamic dispatch version में lower हो जाते हैं। C++ में भी इसे हाथ से implement किया जा सकता है। Type-erased wrapper इस्तेमाल करने वाला template का एक general version बना लीजिए, और अलग-अलग concrete instantiations को explicitly लिखिए। लेकिन यह कठिन और manual है। Swift में बस यही नियम है कि “shared library boundary पर चीज़ें ऐसे काम करती हैं”
Type hiding के साथ भी यही बात है। C++ में library boundary के पार behavior expose करते हुए implementation छिपाने वाली public interface बनाने के लिए
pImplpattern इस्तेमाल होता है। Swift के पास ऐसी abstract machine है जो जानती है कि library boundary कहाँ है, और वह कहती है कि “जो types ABI-stable के रूप में चिह्नित नहीं हैं, उनका size shared library boundary के पार compile-time constant नहीं होता”Standard का वास्तविकता से इनकार करने का यह भी एक रूप है। जिन लगभग सभी non-trivial C++ codebases पर मैंने काम किया है, वे
-fno-rtti -fno-exceptionsया CL.EXE के समकक्ष options के साथ compile हुए थे। Standard इसे एक संभावना के रूप में मान्यता ही नहीं देता। Standard library की ज़्यादातर functions अभी भी error reporting के लिए exceptions पर निर्भर करती हैं, इसलिए-fno-exceptionके साथ compile करने पर वे बसabortकॉल करती हैं। इसका मतलब है कि dynamic memory allocation करने वाले standard library components embedded में उपयोग नहीं किए जा सकते।std::vector<T>::push_backprogram को crash करा सकता हैलेख में यह जो कहा गया है कि “committee न सिर्फ़ खराब features हटा नहीं पाती, बल्कि ऐसे नए features जोड़ती रहती है जिनकी working engineers ने माँग भी नहीं की,” यह contracts के बनने के तरीके से 100% मेल खाता है। Verus दिखाता है कि C++ जैसे environments को target करने वाली language में एक अच्छा contracts system क्या संभव बना सकता है। P2900 contracts परस्पर टकराती requirements का मिश्रण हैं, इसलिए contracts जिन भी समस्याओं में सही बैठ सकते थे, उन सबको यह और बदतर बना देता है
यह निष्कर्ष कि “C++ engineer” को “ऐसा engineer जो programming कर सकता है” से कहीं अधिक वेतन मिलता है, मुझे सही नहीं लगता। हक़ीक़त में कोई भी C++ standard के बिल्कुल अनुसार code नहीं लिखता; हर कोई अपने पसंदीदा in-house subset-of-a-superset के मुताबिक लिखता है
go vetका भी मूल्य है। क्योंकि यह API सुधार के लिए automatic upgrades देता हैपिछले साल से मैंने C++ लगभग छोड़ ही दिया है; पहले Kotlin पर गया, फिर Swift पर। कंपनी में अभी भी C++ maintenance करनी पड़ती है, लेकिन जो नया code लिखता हूँ वह काफ़ी साफ़, संक्षिप्त और सुरक्षित है। Code size और शायद performance में कुछ tradeoff है, लेकिन उसके लायक है
मुझे याद था कि Go के for loop semantics backward compatibility तोड़ते हुए बदले थे, इसलिए मुझे यह कथन ग़लत लगा था: https://go.dev/blog/loopvar-preview
लेकिन बाद में पता चला कि Go यहाँ Rust editions जैसा तरीका अपनाता है। Semantics तभी बदलती हैं जब आप Go version 1.22 या उससे ऊपर declare करते हैं। शायद
io/ioutilको भी इसी तरह हटाया जा सकता था, लेकिन शायद edition boundary पार करके code तोड़ने जितना महत्वपूर्ण मामला वह नहीं थाअगर C++ ने इन बुरे विचारों को वास्तव में आज़मा कर यह साबित न किया होता कि वे बुरे विचार हैं, तो शायद Rust आज जिस रूप में है, उस रूप में मौजूद ही न होता। Big Thank You!
मुझे C++ के लिए Rust-जैसे standard library replacement में दिलचस्पी है। इस लक्ष्य की ओर जाने वाला rpp मुझे पता है: https://github.com/TheNumbat/rpp
क्या और विकल्प हैं? EASTL जैसी C++ stdlib की दूसरी implementations नहीं, बल्कि ऐसी libraries जो Rust का और नज़दीकी अनुसरण करती हों। मुझे पता है कि
std::initializer_listजैसी कुछ चीज़ें syntax में baked-in हैं, लेकिन बाक़ी सब कुछ बदला जा सकता है