- C++ में object initialization
T t;, T t{};, T t{...} जैसे मिलते-जुलते syntax के बावजूद default/value/list/aggregate initialization में बंट जाता है, जिससे असली member state अलग हो सकती है
- default constructor को कहां
= default किया गया है, उसके आधार पर zero-initialization होना या न होना बदल जाता है; class के बाहर define करने पर member uninitialized value के रूप में रह सकते हैं
- जिस type में
const int member हो और लगे कि default constructor delete हो जाएगा, वह अगर aggregate है तो A a{}; aggregate initialization से होकर 0 से initialize हो सकता है
- C++20 का parentheses-based aggregate initialization, braces initialization से ज्यादा permissive है, इसलिए narrowing conversion और dangling reference जैसे फर्क पैदा हो सकते हैं
- वास्तविक काम में implicit constructors और initialization rules के combination पर निर्भर रहने के बजाय direct constructor से initialization का intent दिखाना exceptions से बचने में आसान होता है
C++ initialization syntax जहां अलग-अलग रास्तों में बंटता है
T t; default-initialization करता है
- अगर
T class type है और default constructor है, तो वह constructor चलता है
- अगर
T array है, तो हर element default-initialize होता है
- इसके अलावा कुछ नहीं होता
T t{}; value-initialization जैसा दिखता है, लेकिन braces syntax की वजह से पहले list-initialization के रूप में process होता है
- साधारण उदाहरणों में भी फर्क तुरंत दिख जाता है
int x{}; value-initialized होकर 0 से initialize होता है
int y; default-initialized होता है, इसलिए value initialize नहीं होती
- default constructor वाले
Pair p; और Pair q{}; constructor call करते हैं
- सिर्फ members वाले
SimplePair r; default-initialized होता है, इसलिए members uninitialized रह सकते हैं
default constructor की declaration location से बदलने वाला result
- अगर user constructor declare नहीं करता, तो compiler implicitly-declared default constructor declare करता है
- class के अंदर पहली declaration में
T() = default; लिखने पर standard के हिसाब से इसे implicit declaration constructor के लगभग समान treat किया जाता है
- implicitly declared या explicitly defaulted default constructor delete न हो, तो compiler implicitly-defined default constructor देता है
- implementation के लिहाज से यह खाली body और खाली member initialization list वाले
T() {} के बराबर होना चाहिए
- नीचे दिए code में
t.x 0 हो जाता है
struct T {
int x;
T() = default;
};
T t{};
std::cout << t.x << std::endl;
- वजह यह है कि
t value-initialized होता है, और T का default constructor user-provided नहीं है, इसलिए पहले zero-initialization होता है, फिर default constructor call होता है
class के बाहर = default और user-provided constructor
- वही
= default भी अगर class के बाहर define किया जाए, तो वह user-provided constructor बन जाता है
struct T {
int x;
T();
};
T::T() = default;
T t{};
std::cout << t.x << std::endl;
- यहां
T::T() = default; पहली declaration में defaulted नहीं है, बल्कि class के बाहर defaulted के रूप में define किया गया constructor है
- value-initialization rule के अनुसार अगर user-provided या deleted default constructor हो, तो default-initialization किया जाता है
- इसलिए ऊपर के example में zero-initialization के बिना सिर्फ default constructor चलता है, और वह constructor कुछ नहीं करता, इसलिए
t.x garbage value बन जाता है
default constructor delete होने के मामले
- अगर compiler reasonable default constructor नहीं बना सकता, तो implicitly declared default constructor deleted के रूप में define हो सकता है
- प्रमुख conditions ये हैं
- non-static reference member होना
- ऐसे non-static member या non-abstract base class होना जिन्हें default-construct या destruct करना उचित नहीं है
- बिना default member initializer वाला
const non-static member होना, और वह member const-default-constructible न होना
- अगर user खुद constructor provide करता है, तो compiler implicit default constructor define करने की कोशिश नहीं करता, और deleted constructor भी नहीं बनाता
- ऐसी conditions होने पर भी C++ initialization rules कुल मिलाकर काफी permissive तरीके से काम करते हैं
वह पल जब aggregate initialization बीच में आ जाता है
struct A { const int x; }; A a{}; देखने में ऐसा लगता है कि default constructor delete होकर compile नहीं होगा, लेकिन असल में यह compile होता है और a.x 0 हो जाता है
- मुख्य बात यह है कि
A एक aggregate है
- aggregate या तो array होता है, या ऐसी class होती है जो ये conditions पूरी करे
- user-declared या inherited constructor न हो
- private/protected direct non-static data member न हो
- private/protected direct base class न हो
- virtual function या virtual base class न हो
- aggregate पर list-initialization होने पर, कुछ खास exceptions को छोड़कर aggregate initialization apply होता है
- initialization list का हर element aggregate के elements को क्रम से initialize करता है
- list elements की संख्या कम हो तो बचे हुए elements default member initializer होने पर उससे initialize होते हैं
- default member initializer न हो और reference न हो, तो वे empty initialization list से copy-initialized होते हैं
- इसलिए
A a{}; constructor call नहीं है, बल्कि ऐसा case है जिसमें a.x empty list से copy-list-initialized होता है और value-initialization से होकर zero-initialization होता है
braces initialization के extra rules
T t{...} और T t = {...} जैसे braces-based syntax आम तौर पर list-initialization होते हैं
T t{...} direct-list-initialization है
T t = {...} copy-list-initialization है
- non-aggregate class type में अगर initialization list खाली हो और default constructor हो, तो value-initialization होता है
- इसके अलावा constructors को normal overload resolution से consider किया जाता है, और
std::initializer_list constructor overload को priority मिलती है
- braces initialization एक element से initialize करते समय narrowing conversion allow नहीं करता
struct A {
const int x;
};
A b{4}; // possible
A c{4.0f}; // narrowing conversion error
parentheses initialization ज्यादा permissive है
T t(a, b, c); जैसे parentheses initialization में direct-non-list-initialization call होता है, और यह direct-list-initialization जैसा है लेकिन इसके rules अलग हैं
T t(); object creation नहीं, बल्कि बिना arguments और return type T वाली function declaration के रूप में interpret होता है; इसे most vexing parse problem कहते हैं
- C++20 से aggregate में भी parentheses initialization इस्तेमाल किया जा सकता है
- parentheses-based aggregate initialization, braces-based initialization से अलग behave करता है
- narrowing conversion allow करता है
- बचे हुए elements empty list initialization नहीं, बल्कि सीधे value-initialized होते हैं
- reference से bind किए गए temporary object की lifetime extend नहीं करता
struct T {
const int& r;
};
T t(42);
- ऊपर के code में
t.r dangling reference बन जाता है, और उसे read करने पर undefined behaviour है
std::initializer_list, copy constructor, elision
- parentheses initialization,
std::initializer_list<T> constructor overload होने पर भी T का copy constructor call कर सकता है
- इसके उलट braces initialization में
std::initializer_list constructor को priority मिल सकती है
struct T {
T(std::initializer_list<T>) {
std::cout << "list" << std::endl;
}
T(const T&) {
std::cout << "copy" << std::endl;
}
};
T t{}; // list
T s{t}; // list
T r(t); // copy
T q(T{}); // list, no copy
T q(T{}); में copy न होना इस rule की वजह से है कि direct- और copy-initialization में अगर initializer T type का prvalue है, तो object सीधे उसी expression से initialize होता है
- इस behavior को आम तौर पर copy elision कहा जाता है, लेकिन standard इसे explicitly elision नहीं कहता
- list-initialization में यही elision संभव है या नहीं, इस पर standard की explanation कम है, और CWG issue 2311 इससे जुड़ा है
- GCC और Clang ज्यादातर cases में elision करते हैं
std::initializer_list<T> constructor overload हो तो GCC elision के बजाय उसी constructor का इस्तेमाल करता है
evaluation order और practical conclusion
- parentheses initialization list के elements का evaluation order guaranteed नहीं है
- braces initialization list elements को सख्ती से left-to-right evaluate करती है
- static variable initialization और constant initialization जैसे special rules भी हैं, लेकिन सिर्फ सामान्य object initialization में ही rules काफी complex हैं
- real-world work में implicit default constructor और initialization rules पर निर्भर रहने के बजाय direct constructor लिखकर initialization intent explicit करना ज्यादा सुरक्षित है
1 टिप्पणियां
Hacker News की राय
Tको value initialize करने पर परिणाम 0 होता है—यह व्याख्या सही थी; शुरुआत में यह छूट गया था, लेकिन लेखक वास्तव मेंxको value initialize कर रहे थेकुल मिलाकर C++ के initialization rules 40 वर्षों के विस्तार और पैचवर्क से गुजरते हुए जटिल और कई जगह गैर-सहज हो गए हैं, लेकिन मेरी नज़र में लगभग 99.9% मामलों में वे उम्मीद के मुताबिक काम करते हैं
बड़ा सुधार यह होगा कि default initialization को explicit बनाया जाए, और बाकी सभी मामलों में हमेशा value initialization हो। जब बड़े array को 0 से भरने की लागत से बचना हो, तभी
std::array = void;जैसे syntax से इसे explicit करना बेहतर होगायह ऐसा मामला लगता है जहां कोई बहुत ज्यादा चालाक बनने की कोशिश कर रहा है और फिर शिकायत कर रहा है कि इसे संभालना मुश्किल है
पूरा approach ही गलत है। struct में const reference न रखें; अगर सच में जरूरत हो तो
std::reference_wrapperइस्तेमाल करना सही रहेगाजवाब थोड़ा रूखा है, लेकिन मुख्य बात यह है कि पोस्ट का निष्कर्ष पूरी तरह गलत है। खुद constructors न लिखें, rule of 5/3/0 का पालन करें, और अगर const reference रखना ही है तो यह जांचें कि कहीं rvalue temporary object तो pass नहीं कर रहे। यह इतना डरावना मुद्दा नहीं है
जिसने सिर्फ basic tutorial ही किया हो, वह भी ऐसे बनावटी scenarios से बस बचकर निकल जाएगा
std::reference_wrapperकभी इस्तेमाल नहीं किया, और जिन कई C++ codebases में काम किया है उनमें भी इसे नहीं देखाशायद यह कहीं गहरे और जटिल template code में इस्तेमाल होता होगा, लेकिन बात सही हो सकती है फिर भी मेरे अनुभव में यह common knowledge नहीं है
I Have No Mouth, and I Must Scream (1967)का मूल पाठ यहां देखा जा सकता है: https://talesofmytery.blogspot.com/2018/10/harlan-ellison-i-...Martin H Greenberg का परिवार हमारे घर में ठहरा हुआ था, और वे Martin को ढूंढ रहे थे। मुझे Sci-Fi पसंद था और मैंने उनकी कुछ किताबें भी पढ़ी थीं, लेकिन नाम सुनने के बाद की बातचीत धुंधली-सी याद है। आखिरकार मैंने Martin को जगाकर फोन दे दिया, और उसके बाद सोना मुश्किल हो गया
मुझे हैरानी हुई कि
unlessको bold तक दिखाए गए हिस्से पर कोई तंज भरा comment नहीं थाT t{v0};औरT t{v0, v1};जैसे बढ़िया syntax हैं, लेकिन एक element वाली initialization दो या उससे ज्यादा elements वाले cases की तरह भरोसेमंद ढंग से काम नहीं करतीऐसी language में, जो parameter pack से structs बनाना आसान करने की दिशा में जा रही है और इस तरह initialize हो सकने वाले variable-length array जैसे features भी support करती है, यह फर्क अजीब है। type स्वाभाविक रूप से template भी हो सकता है
आप खुद constructor लिख सकते हैं, और सिर्फ एक element देकर tuple या array initialize भी कर सकते हैं, लेकिन special cases में गलत constructor call हो सकता है। C++11 initialization list जब नई-नई आई थी, तब मैंने यह पाया और सोचा कि यह पागलपन है
C++ की और ज्यादा अजीबोगरीब चीज़ें देखने के लिए C++ FQA सुझाऊंगा: https://yosefk.com/c++fqa/
यह अब लगभग 15 साल पुराना है, लेकिन C++ पुराने features या behaviors को शायद ही हटाता है, इसलिए साथ ही यह timeless भी है
blog theme सचमुच खूबसूरत है। साफ लगता है कि यह DEC era computers से inspired है, फिर भी clean और minimal होने की वजह से fresh लगती है
भयावह है। C++ के डरावने हिस्सों में से एक। language में अच्छे features भी हैं और उस पर शानदार लोग काम करते हैं, इसलिए और अफसोस होता है
उम्मीद है कि Herb की C++ syntax 2 जैसी कोई चीज़ इसे मेरे जैसे आम लोगों के इस्तेमाल लायक language बना देगी
पोस्ट के examples ऐसे बनाए गए हैं कि वे उन special member functions की edge cases को जानबूझकर छेड़ें जिन्हें programmer ने खुद न लिखने का फैसला किया है और जिन्हें language exception के तौर पर auto-generate करती है
C++ का design goal है कि आप जितना इस्तेमाल करें उतनी ही cost चुकाएं, और rule of 3/rule of 5 C++ beginner level की basic knowledge है। ये special member functions compiler सिर्फ कुछ conditions में generate करता है, इसलिए conditions न मिलें तो auto-generate नहीं होंगे—यह भी basics में आता है
आखिर यह जानना कि इस्तेमाल होने वाला constructor खुद set करना चाहिए, इतना बेतुका क्यों है, समझ नहीं आता। instance बनाते समय initialize करना चाहिए—यह भी कोई चौंकाने वाली बात नहीं है। असल दुनिया में लोग बिना बड़े हंगामे के इसी तरह real work कर रहे हैं
इसे पढ़कर चक्कर-सा आ जाता है। वह समय याद आ गया जब Java constructors और object initialization को समझने की कोशिश कर रहा था
कम-से-कम अब तक के अनुभव में, Go और Rust की तरह special constructor न होने का विकल्प बहुत-सी चीज़ों को सरल बना देता है
सोचता हूँ कि constructor न होने वाली भाषा में जाने के बाद क्या किसी को constructors की कमी महसूस हुई है
बेशक
MaybeUninitमेंptr::writeसे fields को एक-एक करके सीधे लिखा जा सकता है, लेकिन यह ergonomic नहीं है और struct को इसे explicit रूप से अनुमति देनी पड़ती हैआखिर इसका मतलब है कि सभी constructors को explicit रूप से declare और define करना पड़ेगा, और C++ भी शुरू से यह विकल्प देता है। special member functions की automatic generation बस उन बहुत specific cases के लिए जोड़ी गई feature है, जहाँ लोग repetitive code से बचना चाहते हैं
चाहें तो अपना constructor जोड़कर सामान्य तरह से काम कर सकते हैं। special member functions की वजह से सरल चीज़ों के जटिल होने की शिकायत करना कुछ वैसा है जैसे dictionary में कठिन शब्द हैं, इसलिए English सरल नहीं है—ऐसी शिकायत करना
मेरी समझ में Rust constructors मूल रूप से Java जैसे ही नहीं हैं?
https://codefibershq.com/blog/golang-why-nil-is-not-always-n...
सोचता हूँ कि क्या कोई C++ tool है जो पीछे होने वाली सारी implicit behavior को जोड़कर या दिखाकर समझाता हो
जैसे automatically added constructors, implicit copy constructors, और बाकी surprising चीज़ें दिखाने वाला tool
cppinsightsका combination ही सबसे अच्छा होगाT::T() = default;के मामले में output 0 होने की उम्मीद होगी, लेकिन example में garbage value आना असल में इतना अजीब नहीं हैसंबंधित link: https://consteval.ca/2024/07/03/initialization/#:~:text=You%...
क्योंकि अगर इसे अलग बनाया जाए, तो library का कोई भी user library behavior बदल सकेगा