T * को रजिस्टर में पास क्यों किया जा सकता है, लेकिन एक अद्वितीय_ टीआरटी <T> नहीं हो सकता है?


85

मैं CppCon 2019 में चैंडलर कारुथ की बात देख रहा हूं:

कोई शून्य लागत सार नहीं हैं

इसमें, वह इस बात का उदाहरण देता है कि कैसे वह आश्चर्यचकित था कि आप एक std::unique_ptr<int>ओवर का उपयोग करके कितना उपरिव्यय करते हैं int*; वह खंड समय बिंदु 17:25 पर शुरू होता है।

आप उनके उदाहरण जोड़ी-ऑफ-स्निपेट्स (Godbolt.org) के संकलन परिणामों पर एक नज़र डाल सकते हैं - साक्षी के लिए, वास्तव में, ऐसा लगता है कि कंपाइलर यूनिक_प्रेट वैल्यू पास करने के लिए इच्छुक नहीं है - जो वास्तव में नीचे की रेखा में है। सिर्फ एक पता - एक रजिस्टर के अंदर, केवल सीधे मेमोरी में।

एक बिंदु श्री कैर्रूथ 27:00 बजे बनाता है कि C ++ ABI को स्मृति में पारित होने के लिए (कुछ लेकिन सभी नहीं; शायद - गैर-आदिम प्रकार के? गैर-आदिम प्रकार के)? बल्कि एक रजिस्टर के भीतर।

मेरे सवाल:

  1. क्या यह वास्तव में कुछ प्लेटफार्मों पर एक एबीआई आवश्यकता है? (जो?) या शायद यह कुछ परिदृश्यों में केवल कुछ निराशा है।
  2. एबीआई ऐसा क्यों है? अर्थात्, यदि रजिस्टर के भीतर एक संरचना / वर्ग के क्षेत्र फिट होते हैं, या यहां तक ​​कि एक भी रजिस्टर - हमें उस रजिस्टर के भीतर पारित करने में सक्षम क्यों नहीं होना चाहिए?
  3. क्या C ++ मानकों की समिति ने हाल के वर्षों में इस बिंदु पर चर्चा की है, या कभी?

पुनश्च - ताकि इस प्रश्न को बिना कोड के न छोड़ा जाए:

सादा सूचक:

void bar(int* ptr) noexcept;
void baz(int* ptr) noexcept;

void foo(int* ptr) noexcept {
    if (*ptr > 42) {
        bar(ptr); 
        *ptr = 42; 
    }
    baz(ptr);
}

अद्वितीय सूचक:

using std::unique_ptr;
void bar(int* ptr) noexcept;
void baz(unique_ptr<int> ptr) noexcept;

void foo(unique_ptr<int> ptr) noexcept {
    if (*ptr > 42) { 
        bar(ptr.get());
        *ptr = 42; 
    }
    baz(std::move(ptr));
}

8
मुझे यकीन नहीं है कि एबीआई की आवश्यकता वास्तव में क्या है, लेकिन यह रजिस्टरों में संरचनाएं लगाने पर प्रतिबंध नहीं
लगाता है

6
अगर मुझे लगता था कि मैं कहूंगा कि इसे गैर-तुच्छ सदस्य के कार्यों के साथ करना होगा जो एक thisपॉइंटर की आवश्यकता है जो एक वैध स्थान पर इंगित करता है। unique_ptrउन है। उस उद्देश्य के लिए रजिस्टर को स्पिल्ट करने से पूरे "पास इन ए रजिस्टर" अनुकूलन को नकार दिया जाएगा।
स्टोरीटेलर - Unslander मोनिका

2
itanium-cxx-abi.github.io/cxx-abi/abi.html#calls । तो इस व्यवहार की आवश्यकता है। क्यों? itanium-cxx-abi.github.io/cxx-abi/cxx-closed.html , समस्या C-7 की खोज करें। वहाँ कुछ स्पष्टीकरण है, लेकिन यह बहुत विस्तृत नहीं है। लेकिन हाँ, यह व्यवहार मुझे तर्कसंगत नहीं लगता। इन वस्तुओं को स्टैक के माध्यम से सामान्य रूप से पारित किया जा सकता है। उन्हें ढेर करने के लिए धक्का देना, और फिर संदर्भ को पारित करना ("गैर-तुच्छ" वस्तुओं के लिए) एक बेकार लगता है।
जेजा

6
ऐसा लगता है कि C ++ यहां अपने ही सिद्धांतों का उल्लंघन कर रहा है, जो काफी दुखद है। मैं 140% आश्वस्त था कि कोई भी अनूठे संकलन के बाद बस गायब हो जाता है। आखिरकार, यह केवल एक आस्थगित विध्वंसक कॉल है जिसे संकलन-समय पर जाना जाता है।
एक आदमी बंदर दस्ते

7
@MaximEgorushkin: अगर आपने इसे हाथ से लिखा होता, तो आप पॉइंटर को एक रजिस्टर में रख देते, न कि स्टैक पर।
ईनपोकलम

जवाबों:


49
  1. क्या यह वास्तव में एक ABI आवश्यकता है, या शायद यह कुछ परिदृश्यों में केवल कुछ निराशा है?

एक उदाहरण सिस्टम वी एप्लीकेशन बाइनरी इंटरफ़ेस AMD64 आर्किटेक्चर प्रोसेसर सप्लीमेंट है । यह ABI 64-बिट x86- संगत CPU (Linux x86_64 Architectecure) के लिए है। यह Solaris, Linux, FreeBSD, macOS, Linux के लिए Windows सबसिस्टम पर अनुसरण किया जाता है:

यदि C ++ ऑब्जेक्ट में या तो एक गैर-तुच्छ प्रतिलिपि निर्माता या एक गैर-तुच्छ विध्वंसक है, तो इसे अदृश्य संदर्भ द्वारा पास किया जाता है (ऑब्जेक्ट को पैरामीटर सूची में एक संकेतक द्वारा प्रतिस्थापित किया जाता है जिसमें वर्ग INTEGER होता है)।

एक गैर-तुच्छ कॉपी निर्माता या एक गैर-तुच्छ विध्वंसक के साथ एक वस्तु को मूल्य से पारित नहीं किया जा सकता है क्योंकि ऐसी वस्तुओं में अच्छी तरह से परिभाषित पते होने चाहिए। किसी फ़ंक्शन से ऑब्जेक्ट वापस करते समय समान समस्याएं लागू होती हैं।

ध्यान दें, कि केवल 2 सामान्य प्रयोजन के रजिस्टरों का उपयोग 1 वस्तु को एक ट्रिवियल कॉपी कंस्ट्रक्टर और एक ट्रिवियल डिस्ट्रक्टर के साथ पारित करने के लिए किया जा सकता है, अर्थात केवल sizeof16 से अधिक ऑब्जेक्ट्स के मूल्यों को रजिस्टरों में पारित नहीं किया जा सकता है। विशेष रूप से ,7.1 पासिंग और लौटने वाली वस्तुओं में कॉलिंग सम्मेलनों के विस्तृत उपचार के लिए एग्नर फॉग द्वारा कॉलिंग कन्वेंशन देखें । रजिस्टरों में SIMD प्रकारों को पास करने के लिए अलग-अलग कॉलिंग कन्वेंशन हैं।

अन्य CPU आर्किटेक्चर के लिए अलग-अलग ABI हैं।


  1. एबीआई ऐसा क्यों है? अर्थात्, यदि रजिस्टर के भीतर एक संरचना / वर्ग के क्षेत्र फिट होते हैं, या यहां तक ​​कि एक भी रजिस्टर - हमें उस रजिस्टर के भीतर पारित करने में सक्षम क्यों नहीं होना चाहिए?

यह एक कार्यान्वयन विवरण है, लेकिन जब स्टैक अनइंडिंग के दौरान एक अपवाद को संभाला जाता है, तो स्वचालित भंडारण अवधि नष्ट होने वाली वस्तुओं को फ़ंक्शन स्टैक फ्रेम के सापेक्ष पता होना चाहिए क्योंकि रजिस्टरों को उस समय तक बंद कर दिया गया है। स्टैक अनइंडिंग कोड को अपने डिस्ट्रक्टर्स को आमंत्रित करने के लिए ऑब्जेक्ट्स के पते की आवश्यकता होती है लेकिन रजिस्टरों में ऑब्जेक्ट्स का पता नहीं होता है।

मूल रूप से, विनाशकारी वस्तुओं पर काम करते हैं :

एक वस्तु अपने पूरे जीवनकाल में, और उसके विनाश की अवधि में, निर्माण की अवधि ([class.cdtor]) में भंडारण के क्षेत्र में रहती है।

और कोई वस्तु C ++ में मौजूद नहीं हो सकती है, यदि इसके लिए कोई पता योग्य भंडारण आवंटित नहीं किया जाता है क्योंकि ऑब्जेक्ट की पहचान उसका पता है

जब रजिस्टरों में रखे गए एक तुच्छ कॉपी निर्माता के साथ किसी ऑब्जेक्ट का पता आवश्यक होता है, तो कंपाइलर ऑब्जेक्ट को मेमोरी में स्टोर कर सकता है और एड्रेस प्राप्त कर सकता है। यदि दूसरी ओर कॉपी कंस्ट्रक्टर गैर-तुच्छ है, तो कंपाइलर इसे मेमोरी में स्टोर नहीं कर सकता है, इसे कॉपी कंस्ट्रक्टर को कॉल करने की आवश्यकता होती है जो एक संदर्भ लेता है और इसलिए रजिस्टरों में ऑब्जेक्ट के पते की आवश्यकता होती है। कॉलिंग कन्वेंशन संभवत: इस बात पर निर्भर नहीं कर सकता है कि कॉपी कंस्ट्रक्टर कैली में इनबिल्ट था या नहीं।

इसके बारे में सोचने का एक और तरीका है, यह है कि तुच्छ रूप से प्रतिलिपि योग्य प्रकारों के लिए कंपाइलर रजिस्टरों में किसी ऑब्जेक्ट के मूल्य को स्थानांतरित करता है , जिससे आवश्यक होने पर एक ऑब्जेक्ट को प्लेन मेमोरी स्टोर द्वारा पुनर्प्राप्त किया जा सकता है। उदाहरण के लिए:

void f(long*);
void g(long a) { f(&a); }

सिस्टम V ABI के साथ x86_64 पर:

g(long):                             // Argument a is in rdi.
        push    rax                  // Align stack, faster sub rsp, 8.
        mov     qword ptr [rsp], rdi // Store the value of a in rdi into the stack to create an object.
        mov     rdi, rsp             // Load the address of the object on the stack into rdi.
        call    f(long*)             // Call f with the address in rdi.
        pop     rax                  // Faster add rsp, 8.
        ret                          // The destructor of the stack object is trivial, no code to emit.

अपने विचार-विमर्श में चांडलर कारुथ ने उल्लेख किया है कि विनाशकारी कदम को लागू करने के लिए एक टूटी हुई एबीआई परिवर्तन (अन्य चीजों के बीच) आवश्यक हो सकता है जो चीजों में सुधार कर सकता है। IMO, ABI परिवर्तन गैर-ब्रेकिंग हो सकता है यदि नए ABI का उपयोग करने वाले फ़ंक्शंस स्पष्ट रूप से एक नया अलग लिंकेज चुनते हैं, उदाहरण के लिए उन्हें extern "C++20" {}ब्लॉक में घोषित करते हैं (संभवतः, मौजूदा API को माइग्रेट करने के लिए एक नए इनलाइन नेमस्पेस में)। ताकि नए लिंकेज के साथ नए फ़ंक्शन की घोषणाओं के खिलाफ संकलित केवल कोड नए एबीआई का उपयोग कर सके।

ध्यान दें कि ABI तब लागू नहीं होता है जब बुलाए गए फ़ंक्शन को इनलाइन किया गया हो। लिंक-टाइम कोड जेनरेशन के साथ-साथ कंपाइलर अन्य ट्रांसलेशन यूनिट में परिभाषित इनलाइन फ़ंक्शन को भी कर सकते हैं या कस्टम कॉलिंग कन्वेंशन का उपयोग कर सकते हैं।


टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
शमूएल एलईवाई

8

आम ABI के साथ, गैर-तुच्छ विध्वंसक -> रजिस्टरों में पास नहीं हो सकता

(@ एक टिप्पणी में @ हेरोल्ड के उदाहरण का उपयोग करके @ MaximEgorushkin के उत्तर में एक बिंदु का चित्रण @ याक की टिप्पणी के अनुसार सही किया गया।)

यदि आप संकलित करते हैं:

struct Foo { int bar; };
Foo test(Foo byval) { return byval; }

आपको मिला:

test(Foo):
        mov     eax, edi
        ret

अर्थात Fooऑब्जेक्ट को testएक रजिस्टर में पास किया जाता है ( edi) और एक रजिस्टर में वापस भी किया जाता है ( eax)।

जब विध्वंसक तुच्छ नहीं है ( std::unique_ptrओपी के उदाहरण की तरह ) - आम एबीआई को स्टैक पर प्लेसमेंट की आवश्यकता होती है। यह तब भी सही है, जब विध्वंसक वस्तु के पते का उपयोग नहीं करता है।

इस प्रकार यहां तक ​​कि अगर आप संकलित करते हैं, तो कुछ भी नहीं विनाशकारी के चरम मामले में:

struct Foo2 {
    int bar;
    ~Foo2() {  }
};

Foo2 test(Foo2 byval) { return byval; }

आपको मिला:

test(Foo2):
        mov     edx, DWORD PTR [rsi]
        mov     rax, rdi
        mov     DWORD PTR [rdi], edx
        ret

बेकार लोडिंग और भंडारण के साथ।


मैं इस तर्क से आश्वस्त नहीं हूं। गैर-तुच्छ विध्वंसक के रूप में अगर नियम को निषिद्ध करने के लिए कुछ भी नहीं करता है। यदि पता नहीं है, तो कोई कारण नहीं है कि एक होने की आवश्यकता क्यों है। इसलिए एक अनुरूप संकलक खुशी से इसे एक रजिस्टर में डाल सकता है, अगर ऐसा करने से अवलोकन योग्य व्यवहार नहीं बदलता है (और वर्तमान संकलक वास्तव में ऐसा करेंगे यदि कॉल करने वाले ज्ञात हैं )।
कॉमिकसंस

1
दुर्भाग्य से, यह दूसरा तरीका है (मैं सहमत हूं कि इसमें से कुछ पहले से ही कारण से परे है)। सटीक होने के लिए: मुझे यकीन नहीं है कि आपके द्वारा प्रदान किए गए कारण आवश्यक रूप से किसी भी अनुमान योग्य एबीआई को प्रस्तुत करेंगे std::unique_ptrजो एक रजिस्टर गैर-अनुरूपता में वर्तमान को पारित करने की अनुमति देता है ।
कॉमिकसंस

3
"तुच्छ विध्वंसक [CITED NEEDED]" स्पष्ट रूप से गलत; यदि कोई कोड वास्तव में पते पर निर्भर नहीं करता है, तो इसका मतलब है कि वास्तविक मशीन पर पते की आवश्यकता नहीं है । पता अमूर्त मशीन में मौजूद होना चाहिए , लेकिन अमूर्त मशीन में जिन चीजों का वास्तविक मशीन पर कोई प्रभाव नहीं पड़ता है वे चीजें हैं जैसे कि खत्म करने की अनुमति है।
यक - एडम नेवेरमोंट

2
@einpoklum मानक में कुछ भी नहीं है जो राज्यों में मौजूद है। रजिस्टर कीवर्ड में कहा गया है "आप पता नहीं ले सकते हैं"। जहां तक ​​मानक का सवाल है, केवल एक अमूर्त मशीन है। "जैसे कि" का अर्थ है कि किसी भी वास्तविक मशीन के कार्यान्वयन के लिए केवल "व्यवहार की आवश्यकता है" जैसे कि अमूर्त मशीन व्यवहार करती है, मानक द्वारा अपरिभाषित व्यवहार तक। अब, एक रजिस्टर में एक वस्तु होने के आसपास बहुत चुनौतीपूर्ण समस्याएं हैं, जिनके बारे में सभी ने बड़े पैमाने पर बात की है। इसके अलावा, सम्मेलनों को बुलाते हुए, जो मानक भी चर्चा नहीं करता है, व्यावहारिक आवश्यकताएं हैं।
यक्क - एडम नेवेरमोंट

1
@einpoklum नहीं, उस अमूर्त मशीन में, सभी चीजों के पते हैं; लेकिन पते केवल कुछ परिस्थितियों में ही देखने योग्य हैं। registerकीवर्ड चीजें हैं जो व्यावहारिक रूप से यह करने के लिए कठिन शारीरिक मशीन में "नहीं पता है" को अवरुद्ध करके इसे एक रजिस्टर में दुकान कुछ करने के लिए भौतिक मशीन के लिए तुच्छ बनाने का इरादा था।
यक्क - एडम नेवेरुमोंट

2

क्या यह वास्तव में कुछ प्लेटफार्मों पर एक एबीआई आवश्यकता है? (जो?) या शायद यह कुछ परिदृश्यों में केवल कुछ निराशा है।

यदि कंप्लीशन यूनिट बाउंड्री पर कुछ दिखाई दे रहा है तो क्या यह स्पष्ट रूप से परिभाषित है या स्पष्ट रूप से यह एबीआई का हिस्सा है।

एबीआई ऐसा क्यों है?

मूल समस्या यह है कि रजिस्टर बच जाते हैं और हर समय बहाल हो जाते हैं क्योंकि आप कॉल स्टैक को नीचे और ऊपर ले जाते हैं। इसलिए उनका संदर्भ या सूचक होना व्यावहारिक नहीं है।

इन-लाइनिंग और इससे होने वाले ऑप्टिमाइज़ेशन अच्छा होता है जब ऐसा होता है, लेकिन एक एबीआई डिज़ाइनर ऐसा होने पर भरोसा नहीं कर सकता है। उन्हें एबीआई को सबसे खराब स्थिति मानते हुए डिजाइन करना होगा। मुझे नहीं लगता कि प्रोग्रामर एक कंपाइलर के साथ बहुत खुश होंगे जहां एबीआई अनुकूलन स्तर के आधार पर बदल गया।

एक तुच्छ रूप से कॉपी करने योग्य प्रकार को रजिस्टरों में पारित किया जा सकता है क्योंकि तार्किक कॉपी ऑपरेशन को दो भागों में विभाजित किया जा सकता है। मापदंडों को कॉलर द्वारा पासिंग मापदंडों के लिए उपयोग किए जाने वाले रजिस्टरों में कॉपी किया जाता है और फिर कैली द्वारा स्थानीय चर में कॉपी किया जाता है। स्थानीय चर में मेमोरी स्थान है या नहीं इस प्रकार केवल कैली की चिंता है।

एक प्रकार जहां कॉपी या मूव कंस्ट्रक्टर को दूसरी ओर इस्तेमाल किया जाना चाहिए, वह इस तरह से कॉपी ऑपरेशन को विभाजित नहीं कर सकता है, इसलिए इसे स्मृति में पारित किया जाना चाहिए।

क्या C ++ मानकों की समिति ने हाल के वर्षों में इस बिंदु पर चर्चा की है, या कभी?

अगर मानकों निकायों ने इस पर विचार किया है तो मुझे कोई अंदाजा नहीं है।

मेरे लिए स्पष्ट समाधान यह है कि लैंगगॉव के लिए उचित विनाशकारी चालें (वर्तमान के "आधे या ज्यादा, लेकिन अन्यथा अनिर्दिष्ट राज्य" के वर्तमान आधे-अधूरे घर को जोड़ने के लिए, फिर "तुच्छ विनाशकारी चाल" की अनुमति देते हुए एक प्रकार का झंडा लगाने का तरीका पेश करें। "भले ही यह तुच्छ प्रतियों के लिए अनुमति नहीं देता है।

लेकिन इस तरह के समाधान के लिए WOULD को मौजूदा प्रकारों को लागू करने के लिए मौजूदा कोड के ABI को तोड़ने की आवश्यकता होती है, जो प्रतिरोध का एक अच्छा हिस्सा ला सकता है (हालांकि नए C ++ मानक संस्करणों के परिणामस्वरूप ABI टूटता नहीं है, उदाहरण के लिए std :: string परिवर्तन C ++ 11 में एक ABI ब्रेक के परिणामस्वरूप ।।


क्या आप इस बात पर विस्तार से बता सकते हैं कि एक रजिस्टर में एक यूनिक_प्रटर के लिए उचित विनाशकारी चालें कैसे चलेंगी? क्या ऐसा इसलिए होगा क्योंकि इससे पता योग्य भंडारण की आवश्यकता समाप्त हो जाएगी?
einpoklum

उचित विनाशकारी चालें तुच्छ विनाशकारी चाल की अवधारणा को पेश करने में सक्षम बनाती हैं। यह कहा जाएगा कि तुच्छ कदम को एबीआई द्वारा उसी तरह से विभाजित किया जा सकता है जिस तरह से आज तुच्छ प्रतियां हो सकती हैं।
प्लगवॉश

हालांकि आप यह भी नियम जोड़ना चाहेंगे कि एक कंपाइलर एक पैरामीटर पास को एक नियमित चाल के रूप में लागू कर सकता है या एक "ट्रिवियल डिस्ट्रक्टिव मूव" के बाद कॉपी कर सकता है ताकि यह सुनिश्चित हो सके कि रजिस्टरों में कोई बात नहीं करना हमेशा संभव था जहां पैरामीटर आया था।
प्लगवेट

क्योंकि रजिस्टर आकार एक पॉइंटर, लेकिन एक यूनिक_प्रटर संरचना धारण कर सकता है? आकार क्या है (unique_ptr <T>)?
मेल विसो मार्टिनेज

@MelVisoMartinez आप भ्रामक unique_ptrऔर shared_ptrशब्दार्थ हो सकते हैं : shared_ptr<T>आपको ctor 1 प्रदान करता है) एक ptr x व्युत्पन्न ऑब्जेक्ट U को स्थैतिक प्रकार U w / अभिव्यक्ति के साथ हटा दिया delete x;जाए (इसलिए आपको यहां वर्चुअल डार्ट की आवश्यकता नहीं है) 2) या यहां तक ​​कि एक कस्टम क्लीनअप फ़ंक्शन। इसका मतलब है कि रनटाइम स्टेट का इस्तेमाल shared_ptrउस जानकारी को एनकोड करने के लिए कंट्रोल ब्लॉक के अंदर किया जाता है । ओटीओएच unique_ptrकी ऐसी कोई कार्यक्षमता नहीं है और राज्य में विलोपन व्यवहार को सांकेतिक नहीं करता है; सफाई को अनुकूलित करने का एकमात्र तरीका एक और टेम्पलेट इंस्टेंशन (एक अन्य वर्ग प्रकार) बनाना है।
जिज्ञासु

-1

पहले हमें मूल्य और संदर्भ से गुजरने का मतलब है।

जावा और एसएमएल जैसी भाषाओं के लिए, मूल्य से पास सीधा है (और संदर्भ से कोई पास नहीं है), जैसा कि एक चर मूल्य की नकल है, जैसा कि सभी चर सिर्फ स्केलर हैं और बिलिन कॉपी अर्थ हैं: वे या तो क्या हैं जो अंकगणित की गणना करते हैं C ++, या "संदर्भ" (विभिन्न नाम और वाक्यविन्यास के साथ संकेत) में टाइप करें।

C में हमारे पास स्केलर और उपयोगकर्ता परिभाषित प्रकार हैं:

  • स्केलरों का एक संख्यात्मक या सार मान होता है (संकेत संख्या नहीं हैं, उनका एक सार मान है) जो कॉपी किया गया है।
  • अलग-अलग प्रकारों में उनके सभी संभवतः प्रारंभिक सदस्यों की प्रतिलिपि बनाई गई है:
    • उत्पाद प्रकारों (सरणियों और संरचनाओं) के लिए: पुनरावर्ती रूप से, सरणियों की संरचना और तत्वों के सभी सदस्यों की प्रतिलिपि बनाई जाती है (सी फ़ंक्शन सिंटैक्स सीधे मान द्वारा सरणियों को पारित करना संभव नहीं बनाता है, केवल एक संरचना के सदस्यों को गिरफ्तार करता है, लेकिन यह एक विवरण है )।
    • सम प्रकार (यूनियनों) के लिए: "सक्रिय सदस्य" का मूल्य संरक्षित है; स्पष्ट रूप से, सदस्य प्रति द्वारा सदस्य क्रम में नहीं है क्योंकि सभी सदस्यों को प्रारंभ नहीं किया जा सकता है।

सी ++ में उपयोगकर्ता परिभाषित प्रकारों में उपयोगकर्ता परिभाषित कॉपी अर्थ हो सकते हैं, जो अपने संसाधनों के स्वामित्व और "गहरी कॉपी" संचालन के साथ वस्तुओं के साथ "ऑब्जेक्ट ओरिएंटेड" वास्तव में प्रोग्रामिंग को सक्षम करते हैं। ऐसे मामले में, एक कॉपी ऑपरेशन वास्तव में एक फ़ंक्शन के लिए एक कॉल है जो लगभग मनमाना संचालन कर सकता है।

C ++ के रूप में संकलित C संरचनाओं के लिए, "कॉपी करना" को अभी भी उपयोगकर्ता परिभाषित कॉपी ऑपरेशन (या तो कंस्ट्रक्टर या असाइनमेंट ऑपरेटर) के रूप में परिभाषित किया गया है, जो संकलक द्वारा उत्पन्न होते हैं। इसका अर्थ है कि C / C ++ के सामान्य सबसेट प्रोग्राम का शब्दार्थ C और C ++ में भिन्न है: C में एक संपूर्ण समुच्चय प्रकार की प्रतिलिपि बनाई गई है, C ++ में प्रत्येक सदस्य को कॉपी करने के लिए एक अंतर्निहित उत्पन्न प्रतिलिपि फ़ंक्शन को कहा जाता है; अंतिम परिणाम यह है कि किसी भी मामले में प्रत्येक सदस्य की नकल की जाती है।

(एक अपवाद है, मुझे लगता है, जब एक संघ के अंदर एक संरचना की नकल की जाती है।)

तो एक वर्ग प्रकार के लिए, एक नया उदाहरण बनाने का एकमात्र तरीका (बाहरी प्रतियां) एक कंस्ट्रक्टर (यहां तक ​​कि तुच्छ संकलित उत्पन्न कंस्ट्रक्टर वाले लोगों के लिए) के माध्यम से है।

आप एक परिचालक के माध्यम से असमान संचालक का पता नहीं लगा सकते हैं, &लेकिन इसका मतलब यह नहीं है कि कोई रव्यू ऑब्जेक्ट नहीं है; और एक वस्तु, परिभाषा से, एक पता है ; और उस पते को एक सिंटैक्स निर्माण द्वारा भी दर्शाया गया है: वर्ग प्रकार की एक वस्तु केवल एक निर्माता द्वारा बनाई जा सकती है, और इसमें एक thisसंकेतक है; लेकिन तुच्छ प्रकारों के लिए, कोई उपयोगकर्ता लिखित रचनाकार नहीं है, इसलिए thisप्रतिलिपि बनाए जाने और नाम दिए जाने तक कोई जगह नहीं है ।

अदिश प्रकार के लिए, किसी वस्तु का मूल्य वस्तु का लकीर है, वस्तु में संग्रहीत शुद्ध गणितीय मूल्य।

एक वर्ग प्रकार के लिए, ऑब्जेक्ट के मूल्य की एकमात्र धारणा ऑब्जेक्ट की एक और कॉपी है, जिसे केवल एक कॉपी कंस्ट्रक्टर द्वारा बनाया जा सकता है, एक वास्तविक फ़ंक्शन (हालांकि तुच्छ प्रकार के लिए जो फ़ंक्शन इतना विशेष रूप से तुच्छ है, ये कभी-कभी हो सकते हैं। कंस्ट्रक्टर को कॉल किए बिना बनाया गया)। इसका मतलब है कि वस्तु का मूल्य एक निष्पादन द्वारा वैश्विक कार्यक्रम राज्य के परिवर्तन का परिणाम है । यह गणितीय रूप से एक्सेस नहीं करता है।

तो मूल्य से गुजरना वास्तव में कोई बात नहीं है: यह कॉपी कंस्ट्रक्टर कॉल से गुजरता है , जो कम सुंदर है। प्रतिलिपि निर्माता से अपेक्षा की जाती है कि वह वस्तु के प्रकार के उचित शब्दार्थ के अनुसार अपने आंतरिक आवेगों (जो कि अमूर्त उपयोगकर्ता गुण हैं, आंतरिक C ++ गुण नहीं) का उचित अर्थ के अनुसार एक समझदार "कॉपी" ऑपरेशन करेगा।

किसी कक्षा वस्तु के मान से गुजरने का अर्थ है:

  • एक और उदाहरण बनाएँ
  • उसके बाद उस फंक्शन पर कॉल फंक्शन एक्ट करें।

ध्यान दें कि समस्या का इस बात से कोई लेना-देना नहीं है कि प्रतिलिपि स्वयं किसी पते वाली वस्तु है: सभी फ़ंक्शन पैरामीटर ऑब्जेक्ट हैं और एक पता (भाषा शब्दार्थ स्तर पर) है।

मुद्दा यह है कि:

  • प्रतिलिपि मूल वस्तु के शुद्ध गणितीय मूल्य (सच्चा शुद्ध अंतराल) के साथ एक नई वस्तु है , जैसा कि स्केलर के साथ;
  • या कॉपी मूल वस्तु का मूल्य है , जैसे कक्षाओं के साथ।

एक तुच्छ वर्ग प्रकार के मामले में, आप अभी भी मूल की सदस्य प्रति के सदस्य को परिभाषित कर सकते हैं, इसलिए आपको प्रतिलिपि संचालन (कॉपी कंस्ट्रक्टर और असाइनमेंट) की तुच्छता के कारण मूल के शुद्ध अंतराल को परिभाषित करना होगा। मनमाने ढंग से विशेष उपयोगकर्ता कार्यों के साथ ऐसा नहीं है: मूल का एक मूल्य एक निर्मित प्रति होना है।

क्लास ऑब्जेक्ट्स को कॉलर द्वारा निर्मित किया जाना चाहिए; एक निर्माणकर्ता के पास औपचारिक रूप से एक thisसंकेतक होता है, लेकिन औपचारिकता यहां प्रासंगिक नहीं है: सभी वस्तुओं का औपचारिक रूप से एक पता होता है, लेकिन केवल वे जो वास्तव में अपना पता गैर विशुद्ध रूप से स्थानीय तरीकों *&i = 1;से उपयोग करते हैं (इसके विपरीत विशुद्ध रूप से स्थानीय उपयोग का पता है) को अच्छी तरह से परिभाषित करने की आवश्यकता है पता।

किसी ऑब्जेक्ट को पते से बिल्कुल पास होना चाहिए, अगर उसे इन दोनों अलग-अलग संकलित कार्यों में एक पता होना चाहिए:

void callee(int &i) {
  something(&i);
}

void caller() {
  int i;
  callee(i);
  something(&i);
}

यहां तक ​​कि अगर something(address)कोई शुद्ध कार्य या मैक्रो या जो भी (जैसे printf("%p",arg)) है जो पते को संग्रहीत नहीं कर सकता है या किसी अन्य इकाई से संवाद नहीं कर सकता है, तो हमें पते से गुजरने की आवश्यकता है क्योंकि पता एक अनूठी वस्तु के लिए अच्छी तरह से परिभाषित किया जाना चाहिए intजो एक अद्वितीय है पहचान।

हम यह नहीं जानते हैं कि कोई बाहरी कार्य इसके लिए दिए गए पतों की अवधि में "शुद्ध" होगा।

यहाँ संभावित या तो एक गैर तुच्छ निर्माता या नाशक में पता की एक वास्तविक उपयोग के लिए फोन करने वाले पक्ष पर शायद सुरक्षित, साधारण मार्ग ले रहे हैं और वस्तु फोन करने वाले इसके पते को एक पहचान देने के लिए और पारित, के रूप में के लिए कारण है यह बनाता है सुनिश्चित करें कि निर्माण में और विध्वंसक के बाद कंस्ट्रक्टर में इसके पते का कोई भी गैर तुच्छ उपयोग सुसंगत है : thisवस्तु के अस्तित्व पर समान होना चाहिए।

किसी अन्य फ़ंक्शन की तरह एक गैर तुच्छ कंस्ट्रक्टर या डिस्ट्रक्टर, thisपॉइंटर को इस तरह से उपयोग कर सकता है कि इसके मूल्य पर निरंतरता की आवश्यकता होती है, भले ही गैर-तुच्छ सामान के साथ कुछ वस्तु न हो:

struct file_handler { // don't use that class!
    file_handler () { this->fileno = -1; }
    file_handler (int f) { this->fileno = f; }
    file_handler (const file_handler& rhs) {
        if (this->fileno != -1)
            this->fileno = dup(rhs.fileno);
        else
            this->fileno = -1;
    }
    ~file_handler () {
        if (this->fileno != -1)
            close(this->fileno); 
    }
    file_handler &operator= (const file_handler& rhs);
};

ध्यान दें कि उस स्थिति में, एक पॉइंटर (स्पष्ट सिंटैक्स this->) के स्पष्ट उपयोग के बावजूद , ऑब्जेक्ट पहचान अप्रासंगिक है: कंपाइलर इसे स्थानांतरित करने और "कॉपी एलीसन" करने के लिए ऑब्जेक्ट को अच्छी तरह से कॉपी कर सकता है। यह thisविशेष सदस्य कार्यों के उपयोग की "शुद्धता" के स्तर पर आधारित है (पता नहीं बचता है)।

लेकिन शुद्धता मानक घोषणा स्तर पर उपलब्ध विशेषता नहीं है (संकलक एक्सटेंशन मौजूद हैं जो गैर इनलाइन फ़ंक्शन घोषणा पर शुद्धता विवरण जोड़ते हैं), इसलिए आप कोड की शुद्धता के आधार पर एक एबीआई को परिभाषित नहीं कर सकते हैं जो उपलब्ध नहीं हो सकता है (कोड हो सकता है या नहीं) इनलाइन नहीं हो सकता है और विश्लेषण के लिए उपलब्ध है)।

पवित्रता को "निश्चित रूप से शुद्ध" या "अशुद्ध या अज्ञात" के रूप में मापा जाता है। सामान्य जमीन, या ऊपरी सीमांतिकी (वास्तव में अधिकतम), या एलसीएम (कम से कम कॉमन मल्टीपल) "अज्ञात" है। तो एबीआई अनजान पर बैठ जाता है।

सारांश:

  • कुछ निर्माणों को वस्तु पहचान को परिभाषित करने के लिए संकलक की आवश्यकता होती है।
  • ABI को कार्यक्रमों के वर्गों के रूप में परिभाषित किया गया है, न कि ऐसे विशिष्ट मामलों को जिन्हें अनुकूलित किया जा सकता है।

भविष्य के संभावित कार्य:

क्या शुद्धता एनोटेशन सामान्यीकृत और मानकीकृत होने के लिए पर्याप्त उपयोगी है?


1
आपका पहला उदाहरण भ्रामक प्रतीत होता है। मुझे लगता है कि आप केवल सामान्य रूप से एक बिंदु बना रहे हैं, लेकिन पहले मुझे लगा कि आप प्रश्न में कोड के अनुरूप बना रहे हैं । लेकिन void foo(unique_ptr<int> ptr)वर्ग वस्तु को मूल्य से लेता है । उस ऑब्जेक्ट में एक पॉइंटर मेंबर होता है, लेकिन हम उस क्लास ऑब्जेक्ट के बारे में बात कर रहे हैं, जिसे संदर्भ द्वारा पास किया जा रहा है। (क्योंकि यह तुच्छ-प्रतिलिपि योग्य नहीं है, इसलिए इसके निर्माता / विध्वंसक को एक सुसंगत की आवश्यकता है this।) यह वास्तविक तर्क है और संदर्भ द्वारा स्पष्ट रूप से पारित होने के पहले उदाहरण से जुड़ा नहीं है ; उस स्थिति में सूचक एक रजिस्टर में पारित हो जाता है।
पीटर कॉर्डेस

@PeterCordes " आप प्रश्न में कोड के लिए एक सादृश्य बना रहे थे। " मैंने ठीक यही किया। " मूल्य द्वारा वर्ग वस्तु " हां मुझे शायद यह समझाना चाहिए कि सामान्य रूप से कोई वर्ग वस्तु के "मूल्य" के रूप में ऐसी कोई चीज नहीं है, इसलिए एक गैर गणित प्रकार के लिए मूल्य "मूल्य से" नहीं है। " उस ऑब्जेक्ट में एक पॉइंटर मेंबर होता है " एक "स्मार्ट ptr" की ptr जैसी प्रकृति अप्रासंगिक है; और इसलिए "स्मार्ट ptr" का ptr सदस्य है। एक ptr सिर्फ एक अदिश की तरह है int: मैंने एक "स्मार्ट फाइलो" उदाहरण लिखा है जो बताता है कि "स्वामित्व" का "ptr ले जाने" से कोई लेना-देना नहीं है।
जिज्ञासु

1
एक वर्ग वस्तु का मूल्य उसका वस्तु-प्रतिनिधित्व है। के लिए unique_ptr<T*>, यह एक ही आकार और लेआउट के रूप में है T*और एक रजिस्टर में फिट बैठता है। सामान्य रूप से कॉपी करने योग्य वर्ग की वस्तुओं को x86-64 सिस्टम V में रजिस्टरों में मूल्य के आधार पर पारित किया जा सकता है , जैसे अधिकांश कॉलिंग कन्वेंशन। यह एक बनाता है प्रतिलिपि की unique_ptrवस्तु, अपने में विपरीत intउदाहरण है जहाँ कॉल प्राप्त करने वाला का &i है फोन करने वाले का पता है iक्योंकि आप संदर्भ द्वारा पारित सी ++ स्तर पर अभी नहीं एक एएसएम कार्यान्वयन विस्तार के रूप में,।
पीटर कॉर्ड्स

1
त्रुटि, मेरी पिछली टिप्पणी में सुधार। यह सिर्फ वस्तु की प्रतिलिपि नहीं बना रहा है unique_ptr; इसका उपयोग कर रहा है std::moveइसलिए इसे कॉपी करना सुरक्षित है क्योंकि इससे 2 प्रतियां नहीं मिलेंगी unique_ptr। लेकिन एक तुच्छ-प्रतिलिपि प्रकार के लिए, हाँ, यह संपूर्ण समुच्चय वस्तु की प्रतिलिपि बनाता है। यदि वह एकल सदस्य है, तो अच्छे कॉलिंग कन्वेंशन इसे उसी प्रकार के स्केलर के रूप में मानते हैं।
पीटर कॉर्डेस

1
बेहतर लग रहा है। नोट्स: C के लिए C ++ के रूप में संकलित संरचनाएं - C ++ के बीच अंतर को पेश करने का यह एक उपयोगी तरीका नहीं है। C ++ struct{}में C ++ स्ट्रक्चर है। शायद आपको "सादे संरचना", या "सी के विपरीत" कहना चाहिए। क्योंकि हाँ, एक अंतर है। यदि आप atomic_intएक संरचना सदस्य के रूप में उपयोग करते हैं , तो C इसे गैर-एटोमिक रूप से कॉपी करेगा, हटाए गए कॉपी कंस्ट्रक्टर पर C ++ त्रुटि। मैं भूल गया कि C ++ volatileसदस्यों के साथ स्ट्रक्चर्स पर क्या करता है । सी आपको struct tmp = volatile_struct;पूरी चीज़ की नकल करने देगा (एक सेक्लो के लिए उपयोगी); C ++ नहीं होगा।
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.