विषय पर बढ़ें

अंतिम अद्यतन:2026-10-12

STL traits टाइप एक्स्ट्रैक्शन

0.प्रस्तावना

STL प्रोग्रामिंग में, कंटेनर तथा एल्गोरिद्म स्वतंत्र रूप से डिज़ाइन किए जाते हैं, अर्थात् डेटा स्ट्रक्चर और एल्गोरिद्म का डिज़ाइन अलग-अलग होता है; कंटेनर तथा एल्गोरिद्म को जोड़ने वाला सेतु इटरेटर है, और इटरेटर ही इस स्वतंत्र डिज़ाइन को संभव बनाता है। नीचे के चित्र में दिखाया गया है:

rela

उपरोक्त चित्र बताता है कि STL का लक्ष्य डेटा तथा एल्गोरिद्म को अलग करना, उन्हें अलग-अलग डिज़ाइन करना, और फिर iterator नामक चीज़ के माध्यम से इन दोनों को दोबारा जोड़ देना है।

डिज़ाइन पैटर्न में iterator के बारे में विवरण इस प्रकार है: एक ऐसी विधि जो कंटेनर के प्रत्येक एलिमेंट तक क्रमवार पहुँच प्रदान करती है, परंतु उस विधि के उपयोग से कंटेनर की आंतरिक अभिव्यक्ति उजागर नहीं होनी चाहिए। और टाइप एक्स्ट्रैक्शन (traits) तकनीक इन्हीं iterator से जुड़ी समस्याओं को हल करने के लिए है।

जब जेनेरिक एल्गोरिद्म (find, count, find_if) किसी कंटेनर पर लागू किए जाते हैं, तो सबसे महत्वपूर्ण बात एल्गोरिद्म को कंटेनर के एलिमेंट्स तक पहुँचने का एक उपकरण देना है, और यह महत्वपूर्ण भूमिका iterator ही निभाता है।

एल्गोरिद्म में हम साधारण मध्यवर्ती वेरिएबल परिभाषित कर सकते हैं या एल्गोरिद्म के रिटर्न वेरिएबल का टाइप निर्धारित कर सकते हैं — ऐसे समय यह जानना ज़रूरी होता है कि इटरेटर जिस एलिमेंट की ओर इशारा करता है उसका टाइप क्या है। परंतु typeof जैसी टाइप-जाँच फंक्शन्स न होने के कारण हम इसे सीधे प्राप्त नहीं कर सकते — तो क्या किया जाए? इसी का विस्तृत विवेचन इस लेख में है।

इटरेटर वस्तुतः एक स्मार्ट पॉइंटर है, इसलिए उसमें सामान्य पॉइंटर की सभी विशेषताएँ होती हैं — उस पर * तथा -> ऑपरेशन किए जा सकते हैं। परंतु कंटेनर को पार करते (traverse) समय उस कंटेनर के भीतर की जानकारी होना अपरिहार्य है; अतः इटरेटर का विकास-कार्य कंटेनर के डिज़ाइनर को सौंप देना ही बेहतर है। इससे सभी कार्यान्वयन विवरण आवरण में रहकर उपयोगकर्ता की दृष्टि से ओझल हो जाते हैं — यही कारण है कि प्रत्येक STL कंटेनर अपना विशिष्ट इटरेटर प्रदान करता है।

Traits bits/stl_iterator_base_types.h में इस प्रकार है:

template<class _Tp>
struct iterator_traits<_Tp*>
{
    typedef ptrdiff_t difference_type;
    typedef typename _Tp::value_type value_type;
    typedef typename _Tp::pointer pointer;
    typedef typename _Tp::reference reference;
    typedef typename _Tp::iterator_category iterator_category;
};

देखकर एकदम अनभिज्ञ लग रहे होंगे, कोई बात नहीं — यह अनुभाग पूरा पढ़कर STL की बुनियादी समझ पा लेंगे, हहा~

1.template पैरामीटर निष्कर्षण

सर्वप्रथम, एल्गोरिद्म में इटरेटर का उपयोग करते समय बहुत संभावना है कि उसके संगत टाइप (associated type) (इटरेटर जिस वस्तु की ओर इशारा करता है उसका टाइप) की आवश्यकता पड़े। मान लीजिए एल्गोरिद्म में एक वेरिएबल घोषित करना आवश्यक हो, जिसका टाइप “इटरेटर जिस ऑब्जेक्ट की ओर इशारा करता है उसका टाइप” हो — तो क्या करें?

समाधान यह है: function template के पैरामीटर निष्कर्षण कार्यप्रणाली का उपयोग करना।

उदाहरण के लिए:

यदि T किसी विशिष्ट ऑब्जेक्ट की ओर इशारा करने वाला पॉइंटर हो, और func में पॉइंटर जिस ऑब्जेक्ट की ओर इशारा करता है उसके टाइप की आवश्यकता हो, तो क्या करें? यह अपेक्षाकृत सरल है — टेम्प्लेट का पैरामीटर निष्कर्षण कार्यप्रणाली यह कार्य पूरा कर सकती है,

template <class I>
inline
void func(I iter) {
    func_impl(iter, *iter); // 传入iter和iter所指的值,class自动推导
}

टेम्प्लेट की निष्कर्षण कार्यप्रणाली से हम बड़ी सरलता से पॉइंटर जिस ऑब्जेक्ट की ओर इशारा करता है उसका टाइप प्राप्त कर लेते हैं।

template <class I, class T>
void func_impl(I iter, T t) {
        T tmp; // 这里就是迭代器所指物的类别
        // ... 功能实现
}

int main() {
    int i;
    func(&i);
}

परंतु, फंक्शन की "template पैरामीटर निष्कर्षण कार्यप्रणाली" केवल पैरामीटर निकालती है, फंक्शन के रिटर्न टाइप का निष्कर्षण नहीं कर सकती। यदि फंक्शन का रिटर्न मान ज्ञात करना हो, तो यह असमर्थ है। इसीलिए हम नीचे की विधि तक आते हैं।

2.एम्बेडेड टाइप घोषित करना

इटरेटर जिस ऑब्जेक्ट की ओर इशारा करता है, उसका टाइप इटरेटर का value type कहलाता है।

यद्यपि func_impl में हम T को फंक्शन के रिटर्न टाइप के रूप में रख सकते हैं, परंतु समस्या यह है कि उपयोगकर्ता को func ही कॉल करना है।

template <class I, class T>
T func_impl(I iter, T t) {
        T tmp; // 这里就是迭代器所指物的类别
        // ... 功能实现
}
template <class T>
(*T) func(T t) { // !!!Wrong code
    return func_impl(t, *t); // forward the task to func_impl
}
int main() {
    int i  =10;
    cout<<func(&i)<<endl; // !!! Can’t pass compile
}

उपरोक्त कोड संकलित करने पर, संकलन असफल हो जाता है!

इस समस्या का समाधान भी कठिन नहीं है — एम्बेडेड टाइप घोषित करना एक अच्छा विचार जान पड़ता है, जिससे हम उसे सीधे प्राप्त कर सकते हैं। बस एक iterator बनाएँ, और परिभाषा के समय उसके इशारा किए ऑब्जेक्ट के टाइप का एक उपनाम निर्धारित कर दें — बस, नीचे दिखाए अनुसार:

template <class T>
struct MyIter {
    typedef T value_type; // 内嵌型别声明
    T* ptr;
    MyIter(T* p = 0) : ptr(p) {}
    T& operator*() const { return *ptr; }
};

template <class I>
typename I::value_type
func(I ite) {
    std::cout << "class version" << std::endl;
    return *ite;
}
int main() {
    // ...
    MyIter<int> ite(new int(8));
    cout << func(ite);  // 输出8
}

बहुत सुंदर समाधान — देखने में सब कुछ एकदम सिद्ध लगता है। परंतु वास्तव में अभी भी समस्या है, क्योंकि यदि func एक जेनेरिक एल्गोरिद्म है, तो उसे इटरेटर के रूप में एक रॉ (native) पॉइंटर भी निश्चित रूप से स्वीकार करना होगा; परंतु स्पष्ट है कि आप नीचे दिए गए कोड को संकलित नहीं करा सकते:

int *p = new int(5);
cout<<func(p)<<endl; // error

हमारा func रॉ पॉइंटर का समर्थन नहीं कर पाता — यह स्पष्टतः अस्वीकार्य है। यहीं template partial specialization काम आता है।

3.मुक्तिदाता Traits

आगे भी उल्लेख हुआ है कि यदि सीधे typename I::value_type का उपयोग करें, तो एल्गोरिद्म रॉ पॉइंटर ग्रहण नहीं कर पाएगा, क्योंकि रॉ पॉइंटर में value_type नामक कोई एम्बेडेड टाइप होता ही नहीं।

इसलिए हमें एक मध्यस्थ परत जोड़कर यह निर्णय करना होगा कि वह रॉ पॉइंटर है या नहीं — ध्यान दें, यही traits तकनीक की सुंदरता है।

यदि हम केवल ऊपर वाला तरीका — यानी एम्बेडेड value_type — अपनाएँ, तो value_type न रखने वाले पॉइंटर्स के लिए हम केवल पार्शियल स्पेशलाइज़ेशन कर सकते हैं; यह पार्शियल स्पेशलाइज़ेशन कॉल किए जाने वाले फंक्शन func के लिए होगा। मान लीजिए func में 10 लाख पंक्तियों का कोड हो, तो इससे भारी दृश्य-प्रदूषण फैलेगा।

(1) फंक्शन पार्शियल स्पेशलाइज़ेशन

फंक्शन पार्शियल स्पेशलाइज़ेशन:

template <class T>
struct MyIter {
    typedef T value_type; // 内嵌型别声明
    T* ptr;
    MyIter(T* p = 0) : ptr(p) {}
    T& operator*() const { return *ptr; }
};

template <class I>
typename I::value_type
func(I ite) {
    std::cout << "class version" << std::endl;
    return *ite;
}
template <class I>
I
func(I* ite) {
    std::cout << "pointer version" << std::endl;
    return *ite;
}
template <class I>
I func(const I* ite) {
    std::cout << "const pointer version" << std::endl;
    return *ite;
}
int main() {
    // ...
    MyIter<int> ite(new int(8));
    cout << func(ite)<<endl;
    int *p = new int(52);
    cout<<func(p)<<endl;
    const int k = 3;
    cout<<func(&k)<<endl;
}

आउटपुट:

class version
8
pointer version
52
const pointer version
3

(2) मध्यस्थ परत जोड़ना

STL में Traits है क्या? नीचे का चित्र देखिए:

trai

एक मध्यस्थ परत iterator_traits के उपयोग से func का स्वरूप निश्चित हो जाता है, जिससे दोहराव वाला कोड बहुत कम हो जाता है; केवल इतना करना है कि iterator_tartis को थोड़ा स्पेशलाइज़ करके pointer तथा const pointer का समर्थन करा दें:)

#include <iostream>

template <class T>
struct MyIter {
    typedef T value_type; // 内嵌型别声明
    T* ptr;
    MyIter(T* p = 0) : ptr(p) {}
    T& operator*() const { return *ptr; }
};
// class type
template <class T>
struct iterator_traits {
    typedef typename T::value_type value_type;
};
// 偏特化1
template <class T>
struct iterator_traits<T*> {
    typedef T value_type;
};
// 偏特化2
template <class T>
struct iterator_traits<const T*> {
    typedef T value_type;
};

template <class I>
typename iterator_traits<I>::value_type
// 首先询问iterator_traits<I>::value_type,如果传递的I为指针,则进入特化版本,iterator_traits直接回答;如果传递进来的I为class type,就去询问T::value_type.
func(I ite) {
    std::cout << "normal version" << std::endl;
    return *ite;
}
int main() {
    // ...
    MyIter<int> ite(new int(8));
    std::cout << func(ite)<<std::endl;
    int *p = new int(52);
    std::cout<<func(p)<<std::endl;
    const int k = 3;
    std::cout<<func(&k)<<std::endl;
}

उपरोक्त प्रक्रिया में सबसे पहले iterator_traits<I>::value_type से पूछा जाता है — यदि पास की गई I पॉइंटर है, तो स्पेशलाइज़्ड संस्करण में प्रवेश होता है और iterator_traits सीधे T उत्तर देता है; यदि पास की गई I एक class type है, तो T::value_type से पूछा जाता है।

इसकी सरल व्याख्या यह है: एल्गोरिद्म (func) iterator_traits (मैं) से पूछता है; जब iterator_traits (मैं) को हाथ में पॉइंटर मिलता है, तो मैं ही उसके लिए उत्तर दे देता हूँ। यदि वह class type है, तो iterator_traits (मैं) आगे पूछता रहता है (उससे --- T::value_type)।

सारांश: एम्बेडेड टाइप परिभाषित करके हमें iterator जिस एलिमेंट की ओर इशारा करता है उसका टाइप जानने की विधि मिल गई; traits तकनीक से हमने रॉ पॉइंटर तथा कस्टम iterator के प्रति फंक्शन टेम्प्लेट की परिभाषाओं को एकीकृत कर दिया। traits तकनीक का उपयोग हम मुख्यतः रॉ पॉइंटर तथा कस्टम iterator के बीच के अंतर से उत्पन्न कोड-अतिरेक को हल करने के लिए करते हैं — यही traits तकनीक की सुंदरता है।

अध्ययन पुस्तक:

侯捷《STL源码剖析》

अध्ययन लेख:

https://juejin.im/post/5b1a43fb51882513bf1795c6

https://www.cnblogs.com/mangoyuan/p/6446046.html

http://www.cppblog.com/nacci/archive/2005/11/03/911.aspx

संबंधित विषय

टिप्पणियाँ