विध्वंसक को दो बार क्यों अंजाम दिया गया?


12
#include <iostream>
using namespace std;

class Car
{
public:
    ~Car()  { cout << "Car is destructed." << endl; }
};

class Taxi :public Car
{
public:
    ~Taxi() {cout << "Taxi is destructed." << endl; }
};

void test(Car c) {}

int main()
{
    Taxi taxi;
    test(taxi);
    return 0;
}

यह आउटपुट है :

Car is destructed.
Car is destructed.
Taxi is destructed.
Car is destructed.

मैं MS Visual Studio सामुदायिक 2017 का उपयोग करता हूं (क्षमा करें, मुझे नहीं पता कि विज़ुअल C ++ का संस्करण कैसे देखा जाए)। जब मैंने डीबग मोड का उपयोग किया। मुझे लगता है कि एक डिस्ट्रक्टर को निष्पादित किया जाता है जब void test(Car c){ }फ़ंक्शन बॉडी को उम्मीद के मुताबिक छोड़ दिया जाता है। और एक अतिरिक्त विध्वंसक दिखाई दिया जब test(taxi);खत्म हो गया है।

test(Car c)समारोह औपचारिक पैरामीटर के रूप में मान का उपयोग करता। फंक्शन में जाने पर कार की कॉपी की जाती है। इसलिए मैंने सोचा कि समारोह से बाहर निकलने पर केवल एक "कार नष्ट हो जाती है" होगी। लेकिन वास्तव में फ़ंक्शन छोड़ते समय दो "कार नष्ट हो जाती है"। (आउटपुट में दिखाए गए अनुसार पहली और दूसरी पंक्ति) दो "कार को क्यों नष्ट किया जाता है"? धन्यवाद।

===============

जब मैं class Car उदाहरण के लिए एक आभासी फ़ंक्शन जोड़ता हूं : virtual void drive() {} तो मुझे अपेक्षित आउटपुट मिलता है।

Car is destructed.
Taxi is destructed.
Car is destructed.

3
एक मुद्दा हो सकता है कि किसी वस्तु को मान द्वारा वस्तु ले जाने पर किसी वस्तु को पास करते समय कंपाइलर ऑब्जेक्ट को कैसे हैंडल करता है ? TaxiCar
कुछ प्रोग्रामर दोस्त

1
आपका पुराना C ++ कंपाइलर होना चाहिए। g ++ 9 अपेक्षित परिणाम देता है। ऑब्जेक्ट की एक अतिरिक्त प्रतिलिपि बनाने का कारण निर्धारित करने के लिए डीबगर का उपयोग करें।
सैम वार्शविक २

2
मैंने संस्करण .0.0 के साथ g ++ और संस्करण 6.0.0 के साथ clang ++ का परीक्षण किया है। उन्होंने अपेक्षित आउटपुट दिया जो ऑप के आउटपुट से अलग है। इसलिए समस्या संकलक के बारे में हो सकती है जो वह उपयोग करता है।
मार्कलाइन

1
मैंने MS Visual C ++ के साथ प्रजनन किया। यदि मैं एक उपयोगकर्ता-निर्धारित कॉपी-कंस्ट्रक्टर और डिफ़ॉल्ट कंस्ट्रक्टर को जोड़ता हूं, Carतो यह समस्या गायब हो जाती है और यह अपेक्षित परिणाम देता है।
अंतरजाल

1
कृपया संकलक और संस्करण को प्रश्न में जोड़ें
लाइटनेस दौड़ ऑर्बिट में

जवाबों:


7

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

सबसे पहले, यह आपके taxiऔर कॉपी-कंस्ट्रक्शन Carसे इसे ले रहा है, ताकि तर्क मेल खाता हो।

फिर, यह पास-दर-मूल्य के लिए Car फिर से कॉपी कर रहा है

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

मुझे नहीं पता कि यह किस हद तक कानूनी है (विशेषकर सी ++ 17 के बाद से), या संकलक इस दृष्टिकोण को क्यों ले जाएगा, लेकिन मैं सहमत हूं कि यह आउटपुट नहीं है जो मुझे सहज रूप से अपेक्षित होगा। न तो जीसीसी और न ही क्लैंग ऐसा करते हैं, हालांकि यह हो सकता है कि वे चीजों को उसी तरह करते हैं लेकिन फिर कॉपी को खत्म करने में बेहतर होते हैं। मैं है देखा है कि यहां तक कि वी.एस. 2019 अभी भी गारंटी इलिजन में महान नहीं है।


क्षमा करें, लेकिन यह बिल्कुल वैसा नहीं है जैसा मैंने "टैक्सी से कार में रूपांतरण के साथ कहा था यदि आपका कंपाइलर कॉपी एलिसन नहीं करता है।"
क्रिस्टोफ

यह एक अनुचित टिप्पणी है, क्योंकि स्लाइसिंग से बचने के लिए रेफ़रेन्स द्वारा पास बनाम वैल्यू पास केवल एक एडिट में जोड़ा गया था, इस सवाल से परे ओपी की मदद करने के लिए। तब मेरा जवाब अंधेरे में एक शॉट नहीं था, यह शुरू से स्पष्ट रूप से समझाया गया था कि यह कहाँ से आ सकता है और मुझे यह देखकर खुशी हुई कि आप उसी निष्कर्ष पर आते हैं। अब आपके फॉर्मूलेशन को देखते हुए, "ऐसा लगता है ... मुझे नहीं पता", मुझे लगता है कि यहां उतनी ही अनिश्चितता है, क्योंकि स्पष्ट रूप से न तो मैं और न ही आप समझते हैं कि संकलक को इस अस्थायी को उत्पन्न करने की आवश्यकता क्यों है।
क्रिस्टोफ़

ठीक है, तो अपने उत्तर के असंबंधित हिस्सों को केवल एक ही संबंधित पैराग्राफ को पीछे छोड़ दें
ऑर्बिट में लाइटनेस दौड़

ठीक है, मैंने ध्यान भटकाने वाले स्लाइसिंग पैरा को हटा दिया है, और मैंने मानक के सटीक संदर्भ के साथ कॉपी एलिसन के बारे में बात को सही ठहराया है।
क्रिस्टोफ

क्या आप बता सकते हैं कि एक अस्थायी कार को टैक्सी से कॉपी-निर्माण क्यों किया जाना चाहिए और फिर दोबारा पैरामीटर में कॉपी किया जाना चाहिए? और सादे कार के साथ प्रदान किए जाने पर कंपाइलर ऐसा क्यों नहीं करता है?
क्रिस्टोफ

3

क्या हो रहा है ?

जब आप एक बनाते हैं Taxi, तो आप एक सब Car-जेक्ट भी बनाते हैं । और जब टैक्सी नष्ट हो जाती है, तो दोनों वस्तुएं नष्ट हो जाती हैं। जब आप कॉल test()करते हैं तो आप Carमूल्य से गुजरते हैं । तो एक दूसरा Carकॉपी-कंस्ट्रक्ट हो जाता है और test()बचे रहने पर नष्ट हो जाएगा । तो हमारे पास 3 विध्वंसक के लिए स्पष्टीकरण है: पहला और दो क्रम में अंतिम।

चौथा विध्वंसक (जो अनुक्रम में दूसरा है) अप्रत्याशित है और मैं अन्य संकलक के साथ पुन: पेश नहीं कर सका।

यह केवल एक अस्थायी तर्क के Carलिए स्रोत के रूप में बनाया जा सकता है Car। चूंकि यह Carतर्क के रूप में सीधे मूल्य प्रदान करते समय नहीं होता है, मुझे संदेह है कि यह रूपांतरित करने के लिए Taxiहै Car। यह अप्रत्याशित है, क्योंकि Carप्रत्येक में पहले से ही एक सब -जेक्ट है Taxi। इसलिए मुझे लगता है कि संकलक एक अस्थायी में एक अनावश्यक रूपांतरण करता है और वह कॉपी एलिसन नहीं करता है जो इस अस्थायी से बचा सकता था।

टिप्पणियों में दिया गया स्पष्टीकरण:

मेरे दावों को सत्यापित करने के लिए भाषा-वकील के मानक के संदर्भ में यहाँ स्पष्टीकरण:

  • मैं जिस रूपांतरण की बात यहां कर रहा हूं, वह कंस्ट्रक्टर द्वारा रूपांतरण है [class.conv.ctor], अर्थात किसी अन्य प्रकार (यहां टैक्सी) के तर्क के आधार पर एक वर्ग (यहां कार) की एक वस्तु का निर्माण।
  • यह रूपांतरण इसके Carमूल्य को वापस करने के लिए एक अस्थायी वस्तु का उपयोग करता है । कंपाइलर को प्रति के अनुसार नकल बनाने की अनुमति दी जाएगी [class.copy.elision]/1.1, क्योंकि यह एक अस्थायी निर्माण के बजाय, यह मान को सीधे पैरामीटर में लौटा सकता है।
  • इसलिए यदि यह अस्थायी दुष्प्रभाव देता है, तो इसका कारण यह है कि संकलक स्पष्ट रूप से इस संभावित प्रति-प्रयोग का उपयोग नहीं करता है। यह गलत नहीं है, क्योंकि कॉपी एलिसेंस अनिवार्य नहीं है।

एसेसिस की प्रायोगिक पुष्टि

मैं अब उसी संकलक का उपयोग करके आपके मामले को पुन: पेश कर सकता हूं और यह पुष्टि करने के लिए एक प्रयोग आकर्षित करूंगा कि क्या हो रहा है।

ऊपर मेरी धारणा यह थी कि कंपाइलर ने सब- सब्जेक्ट Car(const &Taxi)से कॉपी कंस्ट्रक्शन की जगह कंस्ट्रक्टर कन्वर्जन का इस्तेमाल करते हुए सबॉप्टिमल पैरामीटर पासिंग प्रोसेस Carको चुना Taxi

तो मैंने कॉल करने की कोशिश की test()लेकिन स्पष्ट रूप Taxiसे एक में कास्टिंग Car

मेरा पहला प्रयास स्थिति को सुधारने में सफल नहीं हुआ। संकलक ने अभी भी उप-निर्माण निर्माता रूपांतरण का उपयोग किया है:

test(static_cast<Car>(taxi));  // produces the same result with 4 destructor messages

मेरा दूसरा प्रयास सफल हुआ। यह कास्टिंग के रूप में अच्छी तरह से करता है, लेकिन संकलक को दृढ़ता से सुझाव देने Carके लिए Taxiऔर इस मूर्खतापूर्ण अस्थायी ऑब्जेक्ट को बनाए बिना उपयोग करने के लिए सूचक कास्टिंग का उपयोग करता है :

test(*static_cast<Car*>(&taxi));  //  :-)

और आश्चर्य: यह उम्मीद के मुताबिक काम करता है, केवल 3 विनाश संदेश का उत्पादन :-)

समापन प्रयोग:

अंतिम प्रयोग में, मैंने रूपांतरण द्वारा एक कस्टम निर्माता प्रदान किया:

 class Car {
 ... 
     Car(const Taxi& t);  // not necessary but for experimental purpose
 }; 

और इसे लागू करें *this = *static_cast<Car*>(&taxi);। मूर्खतापूर्ण लगता है, लेकिन यह भी कोड उत्पन्न करता है जो केवल 3 विध्वंसक संदेश प्रदर्शित करेगा, इस प्रकार अनावश्यक अस्थायी वस्तु से बचा जाएगा।

इससे लगता है कि संकलक में एक बग हो सकता है जो इस व्यवहार का कारण बनता है। यह कुछ परिस्थितियों में बेस क्लास से प्रत्यक्ष कॉपी-निर्माण की संभावना नहीं है।


2
इस सवाल का जवाब नहीं है
हल्की दौड़

1
@qiazi मुझे लगता है कि यह कॉपी एलिजन के बिना रूपांतरण के लिए अस्थायी की परिकल्पना की पुष्टि करता है, क्योंकि यह अस्थायी कॉलर के संदर्भ में फ़ंक्शन से उत्पन्न होगा।
क्रिस्टोफ

1
जब कह रहे हैं "कार से टैक्सी में रूपांतरण यदि आपका कंपाइलर कॉपी एलिसन नहीं करता है", तो आप किस कॉपी एलीशन की बात कर रहे हैं? ऐसी कोई प्रति नहीं होनी चाहिए, जिसे पहले स्थान पर रखा जाए।
इंटरजेड

1
@interjay क्योंकि संकलक को रूपांतरण करने के लिए टैक्सी के कार उप-ऑब्जेक्ट के आधार पर कार अस्थायी निर्माण की आवश्यकता नहीं है और फिर इस अस्थायी को कार पैरामीटर में कॉपी करें: यह प्रतिलिपि को खत्म कर सकता है और सीधे मूल सबोबिज से पैरामीटर का निर्माण कर सकता है।
क्रिस्टोफ

1
कॉपी एलिज़न तब होता है जब मानक कहता है कि एक कॉपी बनाई जानी चाहिए, लेकिन कुछ परिस्थितियों में कॉपी को एलीर्ड होने की अनुमति मिलती है। इस मामले में पहली बार बनाई जाने वाली प्रतिलिपि के लिए कोई कारण नहीं है (एक संदर्भ Taxiसीधे Carकॉपी निर्माता को पारित किया जा सकता है ), इसलिए कॉपी एलिस अप्रासंगिक है।
इंटरजेंट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.