क्या एक क्लास मेंबर फंक्शन टेम्पलेट वर्चुअल हो सकता है?


304

मैंने सुना है कि C ++ वर्ग के सदस्य फ़ंक्शन टेम्पलेट वर्चुअल नहीं हो सकते। क्या ये सच है?

यदि वे आभासी हो सकते हैं, तो ऐसे परिदृश्य का क्या उदाहरण है जिसमें कोई ऐसे फ़ंक्शन का उपयोग करेगा?


12
मैंने इसी तरह की समस्या का सामना किया, और यह भी सीखा कि एक ही समय में आभासी और टेम्पलेट होना विवादास्पद है। मेरा समाधान टेम्पलेट जादू को लिखना था जो व्युत्पन्न वर्गों के बीच आम होगा और एक विशिष्ट आभासी फ़ंक्शन को कॉल करेगा जो विशेष भाग को करता है। यह मेरी समस्या की प्रकृति से संबंधित है, इसलिए हर मामले में काम नहीं कर सकता।
तमसे सजेलेई

जवाबों:


329

टेम्पलेट कंपाइलर -टाइम पर कंपाइलर जनरेटिंग कोड के बारे में हैं । वर्चुअल फ़ंक्शंस सभी रन-टाइम सिस्टम के बारे में हैं, जो रन-टाइम पर कॉल करने के लिए कार्य करते हैं ।

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

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


32
मैं इसके लिए एक भाषा कारण नहीं देख रहा हूं , केवल कार्यान्वयन कारण। vtables भाषा का हिस्सा नहीं हैं - बस मानक तरीके कंपाइलर भाषा को लागू करते हैं।
जेरार्डव

16
Virtual functions are all about the run-time system figuring out which function to call at run-time- क्षमा करें, लेकिन यह इसके लिए एक गलत तरीका है, और काफी भ्रमित है। यह सिर्फ अप्रत्यक्ष है, और इसमें कोई "रनटाइम फिगरिंग" शामिल नहीं है, यह संकलन समय के दौरान जाना जाता है जिसे कहा जाने वाला फ़ंक्शन vtable में n-th पॉइंटर द्वारा इंगित किया गया एक है। "पता लगाना" से तात्पर्य टाइप जांच और ऐसे हैं, जो मामला नहीं है। Once the run-time system figured out it would need to call a templatized virtual function- फ़ंक्शन वर्चुअल है या नहीं, संकलन समय पर जाना जाता है।
dtech

8
@ddriver: 1. यदि कंपाइलर देखता है void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }, तो यह "जानता है" कि फंक्शन को किस बिंदु पर cb.f()बुलाया जाता है, और वह नहीं जानता है vb.f()। बाद में पता चला हो गया है कार्यावधि में , क्रम प्रणाली द्वारा । चाहे आप इसे "समझ से बाहर" कहना चाहते हैं, और क्या यह अधिक या कम कुशल है, इन तथ्यों को थोड़ा नहीं बदलता है।
22

8
@ddriver: 2. (सदस्य) फ़ंक्शन टेम्प्लेट के उदाहरण (सदस्य) फ़ंक्शन हैं, इसलिए पॉइंटर को इस तरह के इंस्टेंस में डालने के लिए कोई समस्या नहीं है। लेकिन कौन से टेंपरेचर इंस्टैंस की जरूरत होती है, यह तभी पता चलता है जब कॉलर संकलित किया जाता है, जबकि vtables की स्थापना तब की जाती है जब बेस क्लास और व्युत्पन्न वर्ग संकलित किए जाते हैं। और ये सभी अलग-अलग संकलित हैं। इससे भी बदतर - नए व्युत्पन्न वर्गों को रनटाइम पर रनिंग सिस्टम में जोड़ा जा सकता है (लगता है कि आपका ब्राउज़र गतिशील रूप से एक प्लगइन लोड कर रहा है)। यहां तक ​​कि जब नया व्युत्पन्न वर्ग बनाया जाता है तो कॉलर का स्रोत कोड लंबे समय तक खो सकता है।
sbi

9
@ एसएसबी: आप मेरे नाम के आधार पर धारणा क्यों बना रहे हैं? मैंने जेनरिक और टेम्प्लेट को भ्रमित नहीं किया। मुझे पता है कि जावा का जेनरिक शुद्ध रूप से चलता है। आपने C ++ में वर्चुअल मेम्बर फंक्शन टेम्प्लेट क्यों नहीं हो सकते, यह स्पष्ट रूप से नहीं बताया, लेकिन इनक्यूसेटिव ने किया। आपने खाका और आभासी यांत्रिकी को 'समय' बनाम 'रन टाइम' संकलित करने के लिए सरल बनाया और निष्कर्ष निकाला कि "आपके पास आभासी सदस्य फ़ंक्शन टेम्पलेट नहीं हो सकते हैं।" मैंने इनक्यूसिटिव के उत्तर को संदर्भित किया, जो "सी ++ टेम्प्लेट्स द कम्प्लीट गाइड" का संदर्भ देता है। मैं "हाथ लहराते" होना नहीं मानता। आपका दिन शुभ हो।
जवनाटेर

133

C ++ टेम्प्लेट से पूरी गाइड:

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


8
मुझे लगता है कि आज का C ++ कंपाइलर और लिंकर्स, विशेष रूप से लिंक टाइम ऑप्टिमाइज़ेशन सपोर्ट के साथ, लिंक समय पर आवश्यक vtables और ऑफसेट उत्पन्न करने में सक्षम होना चाहिए। तो शायद हमें C ++ 2b में यह सुविधा मिलेगी?
13

33

C ++ अभी वर्चुअल टेम्पलेट सदस्य कार्यों की अनुमति नहीं देता है। सबसे संभावित कारण इसे लागू करने की जटिलता है। राजेंद्र अच्छा कारण देते हैं कि यह अभी क्यों नहीं किया जा सकता है लेकिन यह मानक के उचित परिवर्तनों के साथ संभव हो सकता है। विशेष रूप से काम करते हुए एक अस्थायी फ़ंक्शन के कितने इंस्टेंटिएशन वास्तव में मौजूद हैं और यदि आप वर्चुअल फ़ंक्शन कॉल की जगह पर विचार करते हैं तो वीआईबीटी का निर्माण करना मुश्किल लगता है। मानक लोगों के पास अभी बहुत कुछ करने के लिए बहुत सी अन्य चीजें हैं और सी ++ 1x कंपाइलर लेखकों के लिए भी बहुत काम है।

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


5
@pmr: एक वर्चुअल फ़ंक्शन को कोड से बुलाया जा सकता है जो फ़ंक्शन संकलित होने पर भी मौजूद नहीं था। कंपाइलर यह कैसे निर्धारित करेगा कि कोड के लिए उत्पन्न करने के लिए (सैद्धांतिक) आभासी टेम्पलेट सदस्य फ़ंक्शन के कौन से उदाहरण मौजूद नहीं हैं?
sbi

2
@ एसबी: हां, अलग संकलन एक बहुत बड़ी समस्या होगी। मैं C ++ कंपाइलर का कोई विशेषज्ञ नहीं हूं इसलिए मैं कोई समाधान नहीं दे सकता। सामान्य रूप से टेम्पर्ड कार्यों के साथ इसे हर संकलन इकाई में फिर से त्वरित किया जाना चाहिए, है ना? कि समस्या का समाधान नहीं होगा?
पीर

2
@sbi यदि आप गतिशील रूप से लोड हो रही लाइब्रेरियों का उल्लेख कर रहे हैं, तो यह केवल टेम्पलेट टेम्पलेट / फ़ंक्शंस के साथ सामान्य समस्या है, न कि केवल वर्चुअल टेम्प्लेट विधियों के साथ।
ओक

"सी ++ अनुमति नहीं देता है [...]" - मानक के संदर्भ को देखने के लिए सराहना करेगा (कोई फर्क नहीं पड़ता कि अगर एक तारीख तक जवाब लिखा गया था या आठ साल बाद की तारीख तक है ...)
एकॉनकागुआ

19

वर्चुअल फंक्शन टेबल्स

चलो वर्चुअल फ़ंक्शन तालिकाओं पर कुछ पृष्ठभूमि के साथ शुरू करते हैं और वे कैसे काम करते हैं ( स्रोत ):

[२०.३] आभासी और गैर-आभासी सदस्य कार्यों को कैसे कहा जाता है, इसके बीच क्या अंतर है?

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

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

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

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

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


मेरी समस्या, या मैं यहाँ कैसे आया

मैं अब इस तरह के कुछ का उपयोग करने का प्रयास कर रहा हूँ क्यूमबाइल बेस क्लास के लिए टेम्प्लेटेड ऑप्टिमाइज़्ड लोड फ़ंक्शंस जो अलग-अलग तरह के क्यूब्स के लिए अलग-अलग लागू होंगे (कुछ पिक्सेल द्वारा संग्रहीत, कुछ छवि आदि)।

कुछ कोड:

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

मैं इसे पसंद करना चाहता हूं, लेकिन यह एक आभासी अस्थायी कॉम्बो के कारण संकलित नहीं होगा:

template<class T>
    virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

मैंने कक्षा स्तर तक टेम्पलेट घोषणा को आगे बढ़ाया । इस समाधान से प्रोग्राम को उन विशिष्ट प्रकार के डेटा के बारे में जानने के लिए मजबूर किया जाएगा जो उन्हें पढ़ने से पहले पढ़ेंगे, जो अस्वीकार्य है।

समाधान

चेतावनी, यह बहुत सुंदर नहीं है, लेकिन इसने मुझे पुनरावृत्ति निष्पादन कोड को हटाने की अनुमति दी

1) बेस क्लास में

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

2) और बच्चे कक्षाओं में

void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

template<class T>
void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);

ध्यान दें कि LoadAnyCube को आधार वर्ग में घोषित नहीं किया गया है।


यहां एक काम के साथ एक और स्टैक ओवरफ्लो उत्तर दिया गया है: एक वर्चुअल टेम्पलेट सदस्य वर्कअराउंड की आवश्यकता है


1
मैं एक ही स्थिति, और जन वर्गों की विरासत संरचना से मिला। मैक्रों ने मदद की।
ZFY

16

विंडो 7 पर MinGW G ++ 3.4.5 का उपयोग करके निम्न कोड को संकलित और ठीक से चलाया जा सकता है:

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class A{
public:
    virtual void func1(const T& p)
    {
        cout<<"A:"<<p<<endl;
    }
};

template <typename T>
class B
: public A<T>
{
public:
    virtual void func1(const T& p)
    {
        cout<<"A<--B:"<<p<<endl;
    }
};

int main(int argc, char** argv)
{
    A<string> a;
    B<int> b;
    B<string> c;

    A<string>* p = &a;
    p->func1("A<string> a");
    p = dynamic_cast<A<string>*>(&c);
    p->func1("B<string> c");
    B<int>* q = &b;
    q->func1(3);
}

और आउटपुट है:

A:A<string> a
A<--B:B<string> c
A<--B:3

और बाद में मैंने एक नया वर्ग X जोड़ा:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

जब मैंने कक्षा X को मुख्य रूप से उपयोग करने का प्रयास किया (तो):

X x;
x.func2<string>("X x");

g ++ निम्नलिखित त्रुटि की रिपोर्ट करता है:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

तो यह स्पष्ट है कि:

  • वर्चुअल सदस्य फ़ंक्शन का उपयोग क्लास टेम्पलेट में किया जा सकता है। कंपाइलर के लिए वॉयटेबल का निर्माण करना आसान है
  • क्लास टेम्पलेट सदस्य फ़ंक्शन को वर्चुअल के रूप में परिभाषित करना असंभव है, जैसा कि आप देख सकते हैं, फ़ंक्शन हस्ताक्षर निर्धारित करना और व्यवहार्य प्रविष्टियों को आवंटित करना मुश्किल है।

19
एक क्लास टेम्पलेट में वर्चुअल मेंबर फंक्शन हो सकते हैं। एक सदस्य फ़ंक्शन एक सदस्य फ़ंक्शन टेम्पलेट और वर्चुअल सदस्य फ़ंक्शन दोनों नहीं हो सकता है।
जेम्स मैकनेलिस

1
यह वास्तव में जीसीसी 4.4.3 के साथ विफल रहता है। मेरे सिस्टम पर उबंटू 10.04 के लिए
ब्लूज़किन

3
यह उस सवाल से पूरी तरह से अलग है जो उसने पूछा था। यहां पूरा बेस क्लास टेम्पर्ड है। मैंने पहले भी इस तरह का संकलन किया है। यह विजुअल स्टूडियो 2010 में भी संकलित होगा
ds-bos-msk

14

नहीं, वे नहीं कर सकते। परंतु:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

एक ही प्रभाव है अगर आप सब करना चाहते हैं एक सामान्य इंटरफ़ेस है और उपवर्गों को लागू करना है।


3
यह CRTP के रूप में जाना जाता है अगर कोई उत्सुक है।
माइकल चोई

1
लेकिन यह उन मामलों के लिए मदद नहीं करता है, जहां किसी के पास एक श्रेणी पदानुक्रम है और आधार कक्षाओं के लिए संकेत के आभासी तरीकों को कॉल करने में सक्षम होना चाहता है। आपका Fooपॉइंटर योग्य है Foo<Bar>, यह इंगित नहीं कर सकता है Foo<Barf>या Foo<XXX>
काई पेट्ज़के

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

8

नहीं, टेम्पलेट सदस्य कार्य आभासी नहीं हो सकते।


9
मेरी उत्सुकता है: क्यों? ऐसा करने में कंपाइलर को किन समस्याओं का सामना करना पड़ता है?
WannaBeGeek

1
आपको गुंजाइश में घोषणा की आवश्यकता है (कम से कम, प्रकारों को सही करने के लिए)। मानक (और भाषा) द्वारा आपके द्वारा उपयोग किए जाने वाले पहचानकर्ताओं के लिए गुंजाइश में घोषणा होना आवश्यक है।
dirkgently

4

अन्य उत्तरों में प्रस्तावित टेम्प्लेट फ़ंक्शन एक मुखौटा है और कोई व्यावहारिक लाभ प्रदान नहीं करता है।

  • टेम्प्लेट फ़ंक्शन विभिन्न प्रकारों का उपयोग करके केवल एक बार कोड लिखने के लिए उपयोगी होते हैं।
  • वर्चुअल फ़ंक्शंस विभिन्न वर्गों के लिए एक सामान्य इंटरफ़ेस रखने के लिए उपयोगी हैं।

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

हालांकि प्रत्येक प्रकार के संयोजन के लिए एक डमी आभासी आवरण फ़ंक्शन को परिभाषित करना आवश्यक है:

#include <memory>
#include <iostream>
#include <iomanip>

//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
    virtual void getArea(float &area) = 0;
    virtual void getArea(long double &area) = 0;
};

//---------------------------------------------
// Square
class Square : public Geometry {
public:
    float size {1};

    // virtual wrapper functions call template function for square
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for squares
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(size * size);
    }
};

//---------------------------------------------
// Circle
class Circle : public Geometry  {
public:
    float radius {1};

    // virtual wrapper functions call template function for circle
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for Circles
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(radius * radius * 3.1415926535897932385L);
    }
};


//---------------------------------------------
// Main
int main()
{
    // get area of square using template based function T=float
    std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
    float areaSquare;
    geometry->getArea(areaSquare);

    // get area of circle using template based function T=long double
    geometry = std::make_unique<Circle>();
    long double areaCircle;
    geometry->getArea(areaCircle);

    std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
    return 0;
}

आउटपुट:

स्क्वायर एरिया 1 है, सर्कल एरिया 3.1415926535897932385 है

इसे यहाँ आज़माएँ


3

प्रश्न के दूसरे भाग का उत्तर देने के लिए:

यदि वे आभासी हो सकते हैं, तो ऐसे परिदृश्य का क्या उदाहरण है जिसमें कोई ऐसे फ़ंक्शन का उपयोग करेगा?

यह करने के लिए एक अनुचित बात नहीं है। उदाहरण के लिए, जावा (जहाँ हर विधि आभासी है) में जेनेरिक विधियों की कोई समस्या नहीं है।

वर्चुअल फ़ंक्शन टेम्प्लेट चाहने वाले C ++ में एक उदाहरण एक सदस्य फ़ंक्शन है जो एक जेनेरिक पुनरावृत्ति स्वीकार करता है। या एक सदस्य फ़ंक्शन जो एक सामान्य फ़ंक्शन ऑब्जेक्ट को स्वीकार करता है।

इस समस्या का समाधान है कि बढ़ावा देने के साथ प्रकार erasure का उपयोग करें :: any_range और बढ़ावा :: फ़ंक्शन, जो आपको अपने फ़ंक्शन को टेम्पलेट बनाने की आवश्यकता के बिना एक सामान्य पुनरावृत्ति या फ़ंक्टर को स्वीकार करने की अनुमति देगा।


6
कास्टिंग के लिए जावा जेनरिक सिंटैक्टिक शुगर है। वे टेम्प्लेट के समान नहीं हैं।
Brice M. Dempsey

2
@ BriceM.Dempsey: आप कह सकते हैं कि कास्टिंग एक तरह से जावा इम्प्लाइज जेनेरिक है, बल्कि दूसरी तरह से ... और शब्दार्थ रूप से, उपयोग-केस एक्लिप्सि मान्य IMO है।
einpoklum

2

'वर्चुअल टेम्प्लेट विधि' के लिए एक वर्कअराउंड है यदि टेम्प्लेट विधि के लिए प्रकारों का सेट पहले से ज्ञात है।

विचार दिखाने के लिए, नीचे दिए गए उदाहरण में केवल दो प्रकारों का उपयोग किया जाता है ( intऔर double)।

वहां, एक 'वर्चुअल' टेम्प्लेट मेथड ( Base::Method) संबंधित वर्चुअल मेथड (एक) को कॉल Base::VMethodकरता है, जो टेंपरेचर मेथड इम्प्लीमेंटेशन ( Impl::TMethod) कहता है ।

केवल TMethodव्युत्पन्न कार्यान्वयन ( AImpl, BImpl) और उपयोग में टेम्पलेट पद्धति को लागू करने की आवश्यकता है Derived<*Impl>

class Base
{
public:
    virtual ~Base()
    {
    }

    template <typename T>
    T Method(T t)
    {
        return VMethod(t);
    }

private:
    virtual int VMethod(int t) = 0;
    virtual double VMethod(double t) = 0;
};

template <class Impl>
class Derived : public Impl
{
public:
    template <class... TArgs>
    Derived(TArgs&&... args)
        : Impl(std::forward<TArgs>(args)...)
    {
    }

private:
    int VMethod(int t) final
    {
        return Impl::TMethod(t);
    }

    double VMethod(double t) final
    {
        return Impl::TMethod(t);
    }
};

class AImpl : public Base
{
protected:
    AImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t - i;
    }

private:
    int i;
};

using A = Derived<AImpl>;

class BImpl : public Base
{
protected:
    BImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t + i;
    }

private:
    int i;
};

using B = Derived<BImpl>;

int main(int argc, const char* argv[])
{
    A a(1);
    B b(1);
    Base* base = nullptr;

    base = &a;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;

    base = &b;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;
}

आउटपुट:

0
1
2
3

एनबी: Base::Methodवास्तव में वास्तविक कोड के लिए अधिशेष है ( VMethodइसे सार्वजनिक किया जा सकता है और सीधे उपयोग किया जा सकता है)। मैंने इसे जोड़ा इसलिए यह एक वास्तविक 'वर्चुअल' टेम्प्लेट विधि के रूप में दिखता है।


मैं काम पर एक समस्या को हल करते हुए इस समाधान के साथ आया था। यह मार्क एस्सेल के ऊपर एक के समान लगता है, लेकिन, मुझे आशा है, बेहतर तरीके से लागू किया गया और समझाया गया।
sad1raf

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

Essels दृष्टिकोण पूरी तरह से अलग है: विभिन्न आभासी तात्कालिकता को स्वीकार करने वाले साधारण आभासी कार्य - और व्युत्पन्न वर्ग में अंतिम टेम्पलेट फ़ंक्शन केवल कोड दोहराव से बचने के लिए कार्य करता है और बेस क्लास में काउंटर पार्ट भी नहीं होता है ...
एकॉनगुआ

2

जबकि एक पुराना प्रश्न जिसका उत्तर कई लोगों ने दिया है, मेरा मानना ​​है कि एक सक्सेज विधि, पोस्ट किए गए अन्य लोगों से अलग नहीं है, यह है कि कक्षा की घोषणाओं के दोहराव को कम करने में मदद करने के लिए एक मामूली मैक्रो का उपयोग करना।

// abstract.h

// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
    void render(int a, char *b) override { render_internal<char>(a, b); }   \
    void render(int a, short *b) override { render_internal<short>(a, b); } \
    // ...

class Renderable
{
public:
    // Then, once for each on the abstract
    virtual void render(int a, char *a) = 0;
    virtual void render(int a, short *b) = 0;
    // ...
};

तो अब, हमारे उपवर्ग को लागू करने के लिए:

class Box : public Renderable
{
public:
    IMPL_RENDER() // Builds the functions we want

private:
    template<typename T>
    void render_internal(int a, T *b); // One spot for our logic
};

यहाँ लाभ यह है कि, एक नए समर्थित प्रकार को जोड़ते समय, यह सभी अमूर्त हेडर से किया जा सकता है और संभवतः इसे कई स्रोत / हेडर फ़ाइलों में सुधार कर सकता है।


0

कम से कम gcc 5.4 के साथ वर्चुअल फ़ंक्शंस टेम्पलेट सदस्य हो सकते हैं लेकिन स्वयं टेम्पलेट होने चाहिए।

#include <iostream>
#include <string>
class first {
protected:
    virtual std::string  a1() { return "a1"; }
    virtual std::string  mixt() { return a1(); }
};

class last {
protected:
    virtual std::string a2() { return "a2"; }
};

template<class T>  class mix: first , T {
    public:
    virtual std::string mixt() override;
};

template<class T> std::string mix<T>::mixt() {
   return a1()+" before "+T::a2();
}

class mix2: public mix<last>  {
    virtual std::string a1() override { return "mix"; }
};

int main() {
    std::cout << mix2().mixt();
    return 0;
}

आउटपुट

mix before a2
Process finished with exit code 0

0

इसे इस्तेमाल करे:

Classeder.h में लिखें:

template <typename T>
class Example{
public:
    T c_value;

    Example(){}

    T Set(T variable)
    {
          return variable;
    }

    virtual Example VirtualFunc(Example paraM)
    {
         return paraM.Set(c_value);
    }

जाँच करें, यदि इसके साथ काम कर रहा है, तो इस कोड को main.cpp में लिखें:

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.