मैं कभी-कभी उन प्रोग्रामों को नोटिस करता हूं जो मेरे कंप्यूटर पर त्रुटि के साथ क्रैश होते हैं: "शुद्ध आभासी फ़ंक्शन कॉल"।
जब एक वस्तु एक अमूर्त वर्ग से नहीं बनाई जा सकती है तो ये कार्यक्रम कैसे संकलित करते हैं?
मैं कभी-कभी उन प्रोग्रामों को नोटिस करता हूं जो मेरे कंप्यूटर पर त्रुटि के साथ क्रैश होते हैं: "शुद्ध आभासी फ़ंक्शन कॉल"।
जब एक वस्तु एक अमूर्त वर्ग से नहीं बनाई जा सकती है तो ये कार्यक्रम कैसे संकलित करते हैं?
जवाबों:
यदि आप एक निर्माता या विध्वंसक से वर्चुअल फ़ंक्शन कॉल करने का प्रयास करते हैं, तो वे परिणाम कर सकते हैं। चूँकि आप कंस्ट्रक्टर या डिस्ट्रक्टर से वर्चुअल फंक्शन कॉल नहीं कर सकते हैं (व्युत्पन्न वर्ग ऑब्जेक्ट का निर्माण नहीं किया गया है या पहले ही नष्ट हो चुका है), यह बेस क्लास वर्जन को कॉल करता है, जो कि शुद्ध वर्चुअल फंक्शन के मामले में होता है। 'मौजूद नहीं है।
(लाइव डेमो देखें यहाँ )
class Base
{
public:
Base() { doIt(); } // DON'T DO THIS
virtual void doIt() = 0;
};
void Base::doIt()
{
std::cout<<"Is it fine to call pure virtual function from constructor?";
}
class Derived : public Base
{
void doIt() {}
};
int main(void)
{
Derived d; // This will cause "pure virtual function call" error
}
doIt()
कंस्ट्रक्टर में कॉल आसानी से भक्तिपूर्ण है और सांख्यिकीय Base::doIt()
रूप से प्रेषित की जाती है , जो सिर्फ एक लिंकर त्रुटि का कारण बनता है। हमें वास्तव में एक ऐसी स्थिति की आवश्यकता है जिसमें गतिशील प्रेषण के दौरान गतिशील प्रकार अमूर्त आधार प्रकार है।
Base::Base
एक गैर-आभासी f()
कॉल करें, जो बदले में (शुद्ध) आभासी doIt
विधि को कॉल करता है ।
साथ ही वर्चुअल वर्चुअल फ़ंक्शन को कंस्ट्रक्टर या ऑब्जेक्ट के डिस्ट्रक्टर से शुद्ध वर्चुअल फ़ंक्शंस में कॉल करने के मानक मामले में आप शुद्ध वर्चुअल फ़ंक्शन कॉल (MSVC पर कम से कम) प्राप्त कर सकते हैं यदि आप ऑब्जेक्ट को नष्ट करने के बाद वर्चुअल फ़ंक्शन को कॉल करते हैं। । जाहिर तौर पर यह कोशिश करने और करने के लिए एक बहुत बुरी बात है, लेकिन अगर आप अमूर्त वर्गों के साथ इंटरफेस के रूप में काम कर रहे हैं और आप गड़बड़ करते हैं तो यह कुछ ऐसा है जो आप देख सकते हैं। यह संभवतः अधिक संभावना है यदि आप संदर्भित काउंटेड इंटरफेस का उपयोग कर रहे हैं और आपके पास एक रेफ काउंट बग है या यदि आपके पास एक बहु-थ्रेडेड प्रोग्राम में ऑब्जेक्ट उपयोग / ऑब्जेक्ट विनाश रेस की स्थिति है ... तो इन प्रकार के प्योरकॉल की बात यह है कि यह अक्सर कम करने के लिए आसान है क्या ctor और dtor में आभासी कॉल के 'सामान्य संदिग्धों' के लिए एक चेक के रूप में चल रहा है साफ आ जाएगा।
MSVC के विभिन्न संस्करणों में, आप कर सकते हैं इन प्रकार की समस्याओं को डीबग करने में मदद करने के लिए, रनटाइम लाइब्रेरी के प्योरकॉल हैंडलर को बदलें। आप इस हस्ताक्षर के साथ अपना कार्य प्रदान करके करते हैं:
int __cdecl _purecall(void)
और रनटाइम लाइब्रेरी को लिंक करने से पहले इसे लिंक करना। यह आपको नियंत्रित करता है कि जब एक शुद्ध कॉल का पता चलता है तो क्या होता है। एक बार नियंत्रण करने के बाद आप मानक हैंडलर की तुलना में अधिक उपयोगी कुछ कर सकते हैं। मेरे पास एक हैंडलर है जो स्टैक का पता लगा सकता है कि प्योरसेल कहां हुआ; यहाँ देखें: अधिक जानकारी के लिए http://www.lenholgate.com/blog/2006/01/purecall.html
(ध्यान दें कि आप MSVC के कुछ संस्करणों में अपने हैंडलर को स्थापित करने के लिए _set_purecall_handler () भी कॉल कर सकते हैं)।
_purecall()
जोड़ूंगा : एक आम तौर पर डिलीट किए गए इंस्टेंस के एक तरीके को कॉल करने पर होने वाला इनवोकेशन तब नहीं होगा जब बेस क्लास को __declspec(novtable)
ऑप्टिमाइज़ेशन (Microsoft विशिष्ट) के साथ घोषित किया गया हो । इसके साथ, ऑब्जेक्ट को हटाए जाने के बाद एक ओवरराइड वर्चुअल विधि को कॉल करना पूरी तरह से संभव है, जो समस्या को तब तक मास्क कर सकता है जब तक कि यह आपको किसी अन्य रूप में काटता नहीं है। _purecall()
जाल अपने दोस्त है!
आमतौर पर जब आप किसी झूलने वाले पॉइंटर के जरिए वर्चुअल फंक्शन कहते हैं - तो संभवत: उदाहरण पहले ही नष्ट हो चुका है।
और अधिक "रचनात्मक" कारण हो सकते हैं, भी: शायद आप अपनी वस्तु के उस हिस्से को बंद करने में कामयाब रहे हैं जहां आभासी कार्य को लागू किया गया था। लेकिन आमतौर पर यह सिर्फ इतना है कि उदाहरण पहले ही नष्ट हो चुका है।
मैं इस परिदृश्य में भाग गया कि शुद्ध आभासी कार्यों को नष्ट वस्तुओं के कारण कहा जाता है, Len Holgate
पहले से ही बहुत अच्छा जवाब है , मैं एक उदाहरण के साथ कुछ रंग जोड़ना चाहूंगा:
व्युत्पन्न वर्ग विध्वंसक, बेस क्लास पॉइंट्स को vptr पॉइंट्स को रीसेट करता है, जिसमें शुद्ध वर्चुअल फ़ंक्शन होता है, इसलिए जब हम वर्चुअल फ़ंक्शन को कॉल करते हैं, तो यह वास्तव में शुद्ध वायरल वाले कॉल करता है।
यह स्पष्ट कोड बग, या बहु-थ्रेडिंग वातावरणों में दौड़ की स्थिति का एक जटिल परिदृश्य के कारण हो सकता है।
यहाँ एक सरल उदाहरण है (अनुकूलन के साथ जी ++ संकलन बंद हो गया - एक सरल कार्यक्रम आसानी से दूर अनुकूलित किया जा सकता है):
#include <iostream>
using namespace std;
char pool[256];
struct Base
{
virtual void foo() = 0;
virtual ~Base(){};
};
struct Derived: public Base
{
virtual void foo() override { cout <<"Derived::foo()" << endl;}
};
int main()
{
auto* pd = new (pool) Derived();
Base* pb = pd;
pd->~Derived();
pb->foo();
}
और स्टैक ट्रेस की तरह दिखता है:
#0 0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007ffff749b02a in __GI_abort () at abort.c:89
#2 0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x0000000000400f82 in main () at purev.C:22
हाइलाइट:
यदि ऑब्जेक्ट पूरी तरह से हटा दिया गया है, तो अर्थ डिस्ट्रक्टर कहा जाता है, और मेमोरो पुनः प्राप्त हो जाता है, हम बस प्राप्त कर सकते हैं Segmentation fault
जैसे कि मेमोरी ऑपरेटिंग सिस्टम में वापस आ गई है, और प्रोग्राम बस इसे एक्सेस नहीं कर सकता है। तो यह "शुद्ध आभासी फ़ंक्शन कॉल" परिदृश्य आमतौर पर तब होता है जब ऑब्जेक्ट को मेमोरी पूल पर आवंटित किया जाता है, जबकि एक ऑब्जेक्ट हटा दिया जाता है, अंतर्निहित मेमोरी वास्तव में ओएस द्वारा पुनर्प्राप्त नहीं की जाती है, यह अभी भी प्रक्रिया द्वारा सुलभ है।
मुझे लगता है कि कुछ आंतरिक कारण के लिए अमूर्त वर्ग के लिए एक vtbl बनाया गया है (यह किसी प्रकार की रन टाइम जानकारी के लिए आवश्यक हो सकता है) और कुछ गलत हो जाता है और एक वास्तविक वस्तु मिल जाती है। यह एक बग है। अकेले ही कहना चाहिए कि ऐसा कुछ नहीं हो सकता है।
शुद्ध सट्टा
संपादित करें: ऐसा लगता है कि मैं प्रश्न में मामले में गलत हूं। OTOH IIRC कुछ भाषाएं कंस्ट्रक्टर डिस्ट्रक्टर से vtbl कॉल की अनुमति देती हैं।
मैं VS2010 का उपयोग करता हूं और जब भी मैं सार्वजनिक विधि से सीधे विध्वंसक कॉल करने का प्रयास करता हूं, तो मुझे रनटाइम के दौरान "शुद्ध वर्चुअल फ़ंक्शन कॉल" त्रुटि मिलती है।
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void SomeMethod1() { this->~Foo(); }; /* ERROR */
};
इसलिए मैंने निजी विधि को अलग करने के लिए ~ फू () के अंदर क्या स्थानांतरित किया, फिर इसने एक आकर्षण की तरह काम किया।
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void _MethodThatDestructs() {};
void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
यदि आप Borland / CodeGear / Embarcadero / Idera C ++ बिल्डर का उपयोग करते हैं, तो आप बस लागू कर सकते हैं
extern "C" void _RTLENTRY _pure_error_()
{
//_ErrorExit("Pure virtual function called");
throw Exception("Pure virtual function called");
}
डिबगिंग कोड में एक ब्रेकपॉइंट रखें और आईडीई में कॉलस्टैक देखें, अन्यथा कॉल अपवाद को अपने अपवाद हैंडलर (या फ़ंक्शन) में लॉग इन करें यदि आपके पास उसके लिए उपयुक्त उपकरण हैं। मैं व्यक्तिगत रूप से उसके लिए MadExcept का उपयोग करता हूं।
पुनश्च। मूल फ़ंक्शन कॉल [C ++ बिल्डर] \ source \ cpprtl \ Source \ misc \ pureerr.cpp में है
यहाँ यह होने के लिए एक डरपोक तरीका है। आज मेरे साथ ऐसा अनिवार्य रूप से हुआ था।
class A
{
A *pThis;
public:
A()
: pThis(this)
{
}
void callFoo()
{
pThis->foo(); // call through the pThis ptr which was initialized in the constructor
}
virtual void foo() = 0;
};
class B : public A
{
public:
virtual void foo()
{
}
};
B b();
b.callFoo();
I had this essentially happen to me today
स्पष्ट रूप से सच नहीं है, क्योंकि केवल गलत है: एक शुद्ध आभासी फ़ंक्शन को केवल तभी कहा जाता है जब callFoo()
एक निर्माता (या विध्वंसक) के भीतर बुलाया जाता है, क्योंकि इस समय ए चरण में वस्तु अभी भी (या पहले से ही) है। यहां सिंटैक्स त्रुटि के बिना आपके कोड का एक रनिंग संस्करण है B b();
- कोष्ठक इसे एक फ़ंक्शन घोषणा बनाते हैं, आप एक ऑब्जेक्ट चाहते हैं।