c++ - टेम्पलेट केवल हेडर फ़ाइल में क्यों लागू किया जा सकता है?




templates c++-faq (9)

सी ++ मानक पुस्तकालय से उद्धरण : एक ट्यूटोरियल और हैंडबुक :

इस समय टेम्पलेट्स का उपयोग करने का एकमात्र पोर्टेबल तरीका इनलाइन फ़ंक्शंस का उपयोग कर हेडर फ़ाइलों में उन्हें लागू करना है।

ऐसा क्यों है?

(स्पष्टीकरण: हेडर फाइलें एकमात्र पोर्टेबल समाधान नहीं हैं। लेकिन वे सबसे सुविधाजनक पोर्टेबल समाधान हैं।)


असल में, सी ++ 11 के पहले सी ++ मानक के संस्करणों ने 'निर्यात' कीवर्ड को परिभाषित किया, जिससे हेडर फ़ाइल में टेम्पलेट्स को घोषित करना संभव हो जाता है और उन्हें कहीं और लागू किया जा सकता है।

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

नतीजतन, आईएसओ सी ++ मानक समिति ने सी ++ 11 से शुरू होने वाले टेम्पलेट्स की export सुविधा को हटाने का निर्णय लिया।


इस प्रकार अलग कार्यान्वयन का एक तरीका है।

//inner_foo.h

template <typename T>
struct Foo
{
    void doSomething(T param);
};


//foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}


//foo.h
#include <foo.tpp>

//main.cpp
#include <foo.h>

inner_foo में आगे की घोषणाएं हैं। foo.tpp में कार्यान्वयन है और inner_foo.h शामिल है; foo.tpp को शामिल करने के लिए foo.h में केवल एक पंक्ति होगी।

संकलन समय पर, foo.h की सामग्री foo.tpp पर कॉपी की जाती है और फिर पूरी फ़ाइल को foo.h पर कॉपी किया जाता है जिसके बाद यह संकलित होता है। इस तरह, एक अतिरिक्त फ़ाइल के बदले में कोई सीमा नहीं है, और नामकरण सुसंगत है।

मैं ऐसा इसलिए करता हूं क्योंकि कोड ब्रेक के लिए स्थिर विश्लेषक * .tpp में कक्षा की अगली घोषणाओं को नहीं देखते हैं। किसी भी आईडीई में कोड लिखते समय या YouCompleteMe या दूसरों का उपयोग करते समय यह परेशान है।


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

export कीवर्ड के साथ एक सुविधा थी जिसका उपयोग अलग संकलन के लिए किया जाना था। export सुविधा को C++11 और AFAIK में बहिष्कृत किया गया C++11 , केवल एक संकलक इसे लागू किया गया है। आपको export उपयोग नहीं करना चाहिए। C++ या C++11 में अलग संकलन संभव नहीं है, लेकिन शायद C++17 , अगर अवधारणाएं इसे बनाती हैं, तो हमारे पास अलग संकलन का कोई तरीका हो सकता है।

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

टेम्पलेट्स के लिए अलग संकलन समस्या मुझे लगता है कि यह भी एक समस्या है जो मॉड्यूल के माइग्रेशन के साथ उत्पन्न हो रही है, जो वर्तमान में काम कर रही है।


यदि चिंता अतिरिक्त संकलन समय और द्विआधारी आकार का ब्लाउट है, तो इसका उपयोग करके सभी .cpp मॉड्यूल के हिस्से के रूप में .h को संकलित करके उत्पादित किया गया है, कई मामलों में आप क्या कर सकते हैं टेम्पलेट वर्ग गैर-टेम्पलेटीकृत बेस क्लास से निकलता है इंटरफ़ेस के गैर प्रकार-निर्भर भागों, और बेस क्लास को .cpp फ़ाइल में इसका कार्यान्वयन हो सकता है।


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

चलिए एक स्पष्टीकरण के लिए कंक्रीट के करीब थोड़ा करीब आते हैं। मान लें कि मुझे निम्न फाइलें मिली हैं:

  • foo.h
    • class MyClass<T> के इंटरफ़ेस की घोषणा करता है
  • foo.cpp
    • class MyClass<T> के कार्यान्वयन को परिभाषित करता है
  • bar.cpp
    • MyClass<int> का उपयोग करता है

अलग संकलन का मतलब है कि मैं bar.cpp से स्वतंत्र रूप से foo.cpp संकलित करने में सक्षम होना चाहिए। संकलक प्रत्येक संकलन इकाई पर पूरी तरह स्वतंत्र रूप से विश्लेषण, अनुकूलन और कोड जनरेशन के सभी कड़ी मेहनत करता है; हमें पूरे कार्यक्रम विश्लेषण करने की आवश्यकता नहीं है। यह केवल एक लिंकर है जिसे पूरे कार्यक्रम को एक साथ में संभालने की आवश्यकता है, और लिंकर का काम काफी आसान है।

जब मैं foo.cpp संकलित करता हूं तो bar.cpp को मौजूद होने की भी आवश्यकता नहीं होती है , लेकिन मुझे अभी भी foo.o से लिंक करने में सक्षम होना चाहिए। मैं पहले से ही बार के साथ था। मैंने केवल तभी तैयार किया है, बिना foo recompile सीसीपी foo.cpp को गतिशील लाइब्रेरी में भी संकलित किया जा सकता है, foo.cpp के बिना कहीं और वितरित किया जा सकता है, और foo.cpp लिखने के बाद वे लिखने के बाद कोड से जुड़े हुए हैं

"इंस्टेंटेशन-स्टाइल पॉलिमॉर्फिज्म" का अर्थ है कि टेम्पलेट MyClass<T> वास्तव में एक सामान्य वर्ग नहीं है जिसे कोड में संकलित किया जा सकता है जो T किसी भी मान के लिए काम कर सकता है। इससे मुक्केबाजी जैसे ओवरहेड को जोड़ दिया जाएगा, आवंटकों और कन्स्ट्रक्टरों को फंक्शन पॉइंटर्स पास करने की आवश्यकता है। सी ++ टेम्पलेट्स का इरादा लगभग समान class MyClass_int , class MyClass_float , आदि लिखने से बचने के लिए है, लेकिन अभी भी समाप्त हो सकता है संकलित कोड जो अधिकतर होता है जैसे हमने प्रत्येक संस्करण को अलग से लिखा था । तो एक टेम्पलेट सचमुच एक टेम्पलेट है; एक वर्ग टेम्पलेट एक वर्ग नहीं है, यह प्रत्येक T लिए एक नई कक्षा बनाने के लिए एक नुस्खा है। टेम्पलेट को कोड में संकलित नहीं किया जा सकता है, केवल टेम्पलेट को तुरंत चालू करने का नतीजा संकलित किया जा सकता है।

तो जब foo.cpp संकलित किया जाता है, तो कंपाइलर बार. cpp नहीं देख सकता है कि MyClass<int> की आवश्यकता है। यह टेम्पलेट MyClass<T> देख सकता है, लेकिन यह उस के लिए कोड उत्सर्जित नहीं कर सकता है (यह एक टेम्पलेट है, कक्षा नहीं है)। और जब bar.cpp संकलित किया जाता है, तो संकलक देख सकता है कि इसे MyClass<int> बनाने की आवश्यकता है, लेकिन यह टेम्पलेट MyClass<T> (केवल foo.h में इसका इंटरफ़ेस) नहीं देख सकता है, इसलिए यह नहीं बना सकता यह।

यदि foo.cpp स्वयं MyClass<int> का उपयोग करता है, तो उसके लिए कोड foo.cpp संकलित करते समय उत्पन्न किया जाएगा, इसलिए जब bar.o foo.o से जुड़ा हुआ है, तो उन्हें जोड़ दिया जा सकता है और काम करेगा। हम उस तथ्य का उपयोग एक टेम्पलेट लिखकर टेम्पलेट इंस्टॉलेशन को एक .cpp फ़ाइल में कार्यान्वित करने की अनुमति देने के लिए कर सकते हैं। लेकिन टेम्पलेट के रूप में टेम्पलेट का उपयोग करने के लिए bar.cpp के लिए कोई रास्ता नहीं है और इसे जिस तरह से पसंद है उसे तुरंत चालू करें; यह केवल templated वर्ग के पूर्व-मौजूदा संस्करणों का उपयोग कर सकता है जो foo.cpp के लेखक को प्रदान करने के लिए सोचा था।

आप सोच सकते हैं कि टेम्पलेट को संकलित करते समय कंपाइलर को "सभी संस्करणों को उत्पन्न करना" चाहिए, जिनके साथ कभी भी लिंकिंग के दौरान फ़िल्टर नहीं किया जाता है। विशाल ओवरहेड और चरम कठिनाइयों के अलावा इस तरह के दृष्टिकोण का सामना करना पड़ेगा क्योंकि "टाइप संशोधक" फीचर और एरे जैसे फीचर्स भी अंतर्निहित प्रकारों को अनंत संख्या में वृद्धि करने की इजाजत देते हैं, जब मैं अब अपना प्रोग्राम बढ़ाता हूं तो क्या होता है जोड़ कर:

  • baz.cpp
    • class BazPrivate घोषित करता है और लागू करता है, और MyClass<BazPrivate> का उपयोग करता है

ऐसा कोई संभावित तरीका नहीं है कि यह तब तक काम कर सके जब तक कि हम भी नहीं

  1. हर बार जब हम प्रोग्राम में कोई अन्य फाइल बदलते हैं, तो foo.cpp को फिर से कंपाइल करना होगा , अगर उसने MyClass<T> का नया उपन्यास त्वरण जोड़ा है
  2. आवश्यकता है कि baz.cpp में (संभवतः हेडर के माध्यम से) MyClass<T> का पूरा टेम्पलेट शामिल है, ताकि संकलक MyClass<BazPrivate> के संकलन के दौरान MyClass<BazPrivate> उत्पन्न कर सके

कोई भी पसंद नहीं करता (1), क्योंकि पूरे कार्यक्रम-विश्लेषण संकलन प्रणाली संकलन के लिए हमेशा के लिए लेते हैं, और क्योंकि यह स्रोत कोड के बिना संकलित पुस्तकालयों को वितरित करना असंभव बनाता है। तो हमारे पास (2) इसके बजाय है।


यह बिल्कुल सही है क्योंकि संकलक को यह जानना है कि आवंटन के लिए यह किस प्रकार है। इसलिए टेम्पलेट वर्ग, फ़ंक्शंस, एनम्स इत्यादि को हेडर फ़ाइल में भी कार्यान्वित किया जाना चाहिए यदि इसे सार्वजनिक या पुस्तकालय (स्थिर या गतिशील) का हिस्सा बनाया जाना चाहिए क्योंकि हेडर फ़ाइलों को c / cpp फ़ाइलों के विपरीत संकलित नहीं किया गया है कर रहे हैं। यदि संकलक को पता नहीं है कि यह संकलित नहीं कर सकता है। .Net में ऐसा इसलिए हो सकता है क्योंकि ऑब्जेक्ट क्लास से सभी ऑब्जेक्ट प्राप्त होते हैं। यह नहीं है। नेट।


वास्तव में उन्हें ऑब्जेक्ट कोड में संकलित करने से पहले संकलक द्वारा टेम्पलेट को तत्काल करने की आवश्यकता होती है। यह तात्कालिकता केवल तभी प्राप्त की जा सकती है जब टेम्पलेट तर्क ज्ञात हों। अब एक परिदृश्य की कल्पना करें जहां a.cpp में परिभाषित ah में टेम्पलेट फ़ंक्शन घोषित किया गया है और b.cpp में उपयोग किया जाता है। जब a.cpp संकलित किया जाता है, तो यह आवश्यक नहीं है कि आने वाले संकलन b.cpp को टेम्पलेट के उदाहरण की आवश्यकता होगी, अकेले ही यह विशिष्ट उदाहरण होगा। अधिक हेडर और स्रोत फ़ाइलों के लिए, स्थिति जल्दी से अधिक जटिल हो सकती है।

कोई तर्क दे सकता है कि टेम्पलेट के सभी उपयोगों के लिए कंपाइलर्स को "आगे देखो" के लिए बेहतर बनाया जा सकता है, लेकिन मुझे यकीन है कि रिकर्सिव या अन्यथा जटिल परिदृश्य बनाना मुश्किल नहीं होगा। AFAIK, कंपाइलर्स इस तरह के देखो आगे नहीं करते हैं। जैसा कि एंटोन ने बताया, कुछ कंपाइलर टेम्पलेट इंस्टॉलेशन के स्पष्ट निर्यात घोषणाओं का समर्थन करते हैं, लेकिन सभी कंपाइलर्स इसका समर्थन नहीं करते हैं (अभी तक?)।


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

मतलब typename T को संकलन चरण के दौरान प्रतिस्थापित किया गया है, जो लिंकिंग चरण नहीं है, इसलिए यदि मैं बिना किसी टेम्पलेट को संकलित करने की कोशिश करता हूं, T को कंक्रीट वैल्यू टाइप के रूप में प्रतिस्थापित किया जाता है, तो यह काम नहीं करेगा क्योंकि यह टेम्पलेट्स की परिभाषा है, यह एक संकलित समय प्रक्रिया है, और बीटीडब्ल्यू मेटा-प्रोग्रामिंग इस परिभाषा का उपयोग करने के बारे में है।


हेडर फ़ाइल में कार्यान्वयन करना आवश्यक नहीं है, इस उत्तर के अंत में वैकल्पिक समाधान देखें।

वैसे भी, आपका कोड असफल होने का कारण यह है कि, टेम्पलेट को चालू करते समय, कंपाइलर दिए गए टेम्पलेट तर्क के साथ एक नई कक्षा बनाता है। उदाहरण के लिए:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

इस लाइन को पढ़ने पर, कंपाइलर एक नई कक्षा बनाएगा (चलिए इसे FooInt कहते हैं), जो निम्न के बराबर है:

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

नतीजतन, संकलक को टेम्पलेट तर्क (इस मामले में int ) के साथ उन्हें तुरंत चालू करने के लिए विधियों के कार्यान्वयन तक पहुंच की आवश्यकता होती है। यदि ये कार्यान्वयन शीर्षलेख में नहीं थे, तो वे पहुंच योग्य नहीं होंगे, और इसलिए संकलक टेम्पलेट को तुरंत चालू नहीं कर पाएंगे।

इसका एक सामान्य समाधान हेडर फ़ाइल में टेम्पलेट घोषणा लिखना है, फिर कक्षा को कार्यान्वयन फ़ाइल (उदाहरण के लिए .tpp) में लागू करें, और हेडर के अंत में इस कार्यान्वयन फ़ाइल को शामिल करें।

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

इस तरह, कार्यान्वयन अभी भी घोषणा से अलग है, लेकिन संकलक के लिए सुलभ है।

एक और समाधान कार्यान्वयन को अलग रखना है, और आपको आवश्यक सभी टेम्पलेट उदाहरणों को स्पष्ट रूप से तुरंत चालू करना है:

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------    
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

यदि मेरी व्याख्या पर्याप्त स्पष्ट नहीं है, तो आप इस विषय पर सी ++ सुपर-एफएक्यू पर एक नज़र डाल सकते हैं।





c++-faq