इरेज़र तकनीक टाइप करें


136

(प्रकार के क्षरण के साथ, मेरा मतलब है कि वर्ग के बारे में कुछ या सभी प्रकार की जानकारी को छिपाना , कुछ हद तक Boost.Any की तरह ।)
मैं तकनीकों की एक पकड़ प्राप्त करना चाहता हूं, जबकि उन लोगों को भी साझा करता हूं, जिनके बारे में मुझे पता है। मेरी आशा थोड़े पागल तकनीक को खोजने की है, जो किसी ने अपने अंधेरे घंटे में सोचा था। :)

पहला और सबसे स्पष्ट, और आमतौर पर लिया गया दृष्टिकोण, जिसे मैं जानता हूं, आभासी कार्य हैं। इंटरफ़ेस आधारित कक्षा पदानुक्रम के अंदर अपनी कक्षा के कार्यान्वयन को छिपाएँ। कई बूस्ट लाइब्रेरी ऐसा करते हैं, उदाहरण के लिए Boost.Any यह आपके प्रकार को छिपाने के लिए करता है और Boost.Saring_ptr यह (डी) आवंटन मैकेनिक को छिपाने के लिए करता है।

फिर फंक्शन पॉइंटर्स के साथ विकल्प टेम्पर्ड फंक्शंस के लिए होता है , जबकि वास्तविक ऑब्जेक्ट को void*पॉइंटर में रखने पर, जैसे Boost.Function असली प्रकार के फ़नकार को छिपाने के लिए करता है। उदाहरण कार्यान्वयन प्रश्न के अंत में पाए जा सकते हैं।

तो, मेरे वास्तविक प्रश्न के लिए: आपको
किस प्रकार की अन्य तकनीक के बारे में पता है? कृपया उन्हें प्रदान करें, यदि संभव हो तो, उदाहरण कोड के साथ, मामलों का उपयोग करें, उनके साथ अपने अनुभव और शायद आगे पढ़ने के लिए लिंक।

संपादित करें
(क्योंकि मुझे यकीन नहीं था कि इसे उत्तर के रूप में जोड़ना होगा, या बस प्रश्न को संपादित करें, मैं सिर्फ सुरक्षित करूंगा।) आभासी कार्यों या फ़िडलिंग के बिना
वास्तविक प्रकार को छिपाने के लिए एक और अच्छी तकनीक है। एक GMan मेरे प्रश्न की प्रासंगिकता के साथ यहाँ कार्यरत हैvoid* काम करता है, यह कैसे काम करता है पर ।


उदाहरण कोड:

#include <iostream>
#include <string>

// NOTE: The class name indicates the underlying type erasure technique

// this behaves like the Boost.Any type w.r.t. implementation details
class Any_Virtual{
        struct holder_base{
                virtual ~holder_base(){}
                virtual holder_base* clone() const = 0;
        };

        template<class T>
        struct holder : holder_base{
                holder()
                        : held_()
                {}

                holder(T const& t)
                        : held_(t)
                {}

                virtual ~holder(){
                }

                virtual holder_base* clone() const {
                        return new holder<T>(*this);
                }

                T held_;
        };

public:
        Any_Virtual()
                : storage_(0)
        {}

        Any_Virtual(Any_Virtual const& other)
                : storage_(other.storage_->clone())
        {}

        template<class T>
        Any_Virtual(T const& t)
                : storage_(new holder<T>(t))
        {}

        ~Any_Virtual(){
                Clear();
        }

        Any_Virtual& operator=(Any_Virtual const& other){
                Clear();
                storage_ = other.storage_->clone();
                return *this;
        }

        template<class T>
        Any_Virtual& operator=(T const& t){
                Clear();
                storage_ = new holder<T>(t);
                return *this;
        }

        void Clear(){
                if(storage_)
                        delete storage_;
        }

        template<class T>
        T& As(){
                return static_cast<holder<T>*>(storage_)->held_;
        }

private:
        holder_base* storage_;
};

// the following demonstrates the use of void pointers 
// and function pointers to templated operate functions
// to safely hide the type

enum Operation{
        CopyTag,
        DeleteTag
};

template<class T>
void Operate(void*const& in, void*& out, Operation op){
        switch(op){
        case CopyTag:
                out = new T(*static_cast<T*>(in));
                return;
        case DeleteTag:
                delete static_cast<T*>(out);
        }
}

class Any_VoidPtr{
public:
        Any_VoidPtr()
                : object_(0)
                , operate_(0)
        {}

        Any_VoidPtr(Any_VoidPtr const& other)
                : object_(0)
                , operate_(other.operate_)
        {
                if(other.object_)
                        operate_(other.object_, object_, CopyTag);
        }

        template<class T>
        Any_VoidPtr(T const& t)
                : object_(new T(t))
                , operate_(&Operate<T>)
        {}

        ~Any_VoidPtr(){
                Clear();
        }

        Any_VoidPtr& operator=(Any_VoidPtr const& other){
                Clear();
                operate_ = other.operate_;
                operate_(other.object_, object_, CopyTag);
                return *this;
        }

        template<class T>
        Any_VoidPtr& operator=(T const& t){
                Clear();
                object_ = new T(t);
                operate_ = &Operate<T>;
                return *this;
        }

        void Clear(){
                if(object_)
                        operate_(0,object_,DeleteTag);
                object_ = 0;
        }

        template<class T>
        T& As(){
                return *static_cast<T*>(object_);
        }

private:
        typedef void (*OperateFunc)(void*const&,void*&,Operation);

        void* object_;
        OperateFunc operate_;
};

int main(){
        Any_Virtual a = 6;
        std::cout << a.As<int>() << std::endl;

        a = std::string("oh hi!");
        std::cout << a.As<std::string>() << std::endl;

        Any_Virtual av2 = a;

        Any_VoidPtr a2 = 42;
        std::cout << a2.As<int>() << std::endl;

        Any_VoidPtr a3 = a.As<std::string>();
        a2 = a3;
        a2.As<std::string>() += " - again!";
        std::cout << "a2: " << a2.As<std::string>() << std::endl;
        std::cout << "a3: " << a3.As<std::string>() << std::endl;

        a3 = a;
        a3.As<Any_Virtual>().As<std::string>() += " - and yet again!!";
        std::cout << "a: " << a.As<std::string>() << std::endl;
        std::cout << "a3->a: " << a3.As<Any_Virtual>().As<std::string>() << std::endl;

        std::cin.get();
}

1
"प्रकार क्षरण" से, क्या आप वास्तव में "बहुरूपता" का उल्लेख कर रहे हैं? मुझे लगता है कि "टाइप इरेज़र" का कुछ विशिष्ट अर्थ है, जो आमतौर पर उदाहरण के लिए जावा जेनरिक से जुड़ा होता है।
ओलिवर चार्ल्सवर्थ

3
@ ओली: प्रकार के उन्मूलन को बहुरूपता के साथ लागू किया जा सकता है, लेकिन यह एकमात्र विकल्प नहीं है, मेरा दूसरा उदाहरण दिखाता है। :) और प्रकार erasure के साथ मेरा सिर्फ इतना मतलब है, कि आपकी संरचना उदाहरण के लिए टेम्पलेट प्रकार पर निर्भर नहीं है। Boost.Function परवाह नहीं करता है अगर आप इसे एक फ़नकार, एक फ़ंक्शन पॉइंटर या एक लैम्ब्डा खिलाते हैं। Boost.Saring_Ptr के साथ भी। आप एक आबंटक और डिक्लोकेशन फ़ंक्शन को निर्दिष्ट कर सकते हैं, लेकिन वास्तविक प्रकार shared_ptrइस को प्रतिबिंबित नहीं करता है, यह हमेशा एक ही होगा, shared_ptr<int>उदाहरण के लिए, मानक कंटेनर के विपरीत।
Xeo

2
@ मैथ्यू: मेरा मानना ​​है कि दूसरा उदाहरण भी सुरक्षित है। आप हमेशा उस सटीक प्रकार को जानते हैं जिस पर आप काम कर रहे हैं। या क्या मैं कुछ न कुछ भूल रहा हूं?
Xeo

2
@ मैथ्यू: आप सही कह रहे हैं। आम तौर पर इस तरह के एक As(एस) समारोह उस तरह से लागू नहीं किया जाएगा। जैसा कि मैंने कहा, किसी भी तरह से सुरक्षित उपयोग के लिए नहीं! :)
Xio

4
@lurscher: ठीक है ... कभी भी निम्नलिखित में से किसी के भी बूस्ट या एसटीडी संस्करण का इस्तेमाल नहीं किया ? function, shared_ptr, any, आदि? वे सभी मीठे मीठे उपयोगकर्ता सुविधा के लिए टाइप इरेज़र नियुक्त करते हैं।
Xeo

जवाबों:


100

C ++ में सभी प्रकार के इरेज़र तकनीक फ़ंक्शन पॉइंटर्स (व्यवहार के लिए) और void*(डेटा के लिए) के साथ किए जाते हैं। "अलग" विधियां बस उस तरह से भिन्न होती हैं जैसे वे शब्दार्थ चीनी को जोड़ते हैं। उदाहरण के लिए, वर्चुअल फ़ंक्शंस, केवल शब्दार्थ चीनी हैं

struct Class {
    struct vtable {
        void (*dtor)(Class*);
        void (*func)(Class*,double);
    } * vtbl
};

iow: फंक्शन पॉइंटर्स।

उस ने कहा, एक तकनीक है जो मुझे विशेष रूप से पसंद है, हालांकि: यह shared_ptr<void>सिर्फ इसलिए है क्योंकि यह उन लोगों के दिमाग को उड़ा देता है जो नहीं जानते कि आप ऐसा कर सकते हैं: आप किसी भी डेटा को एक में संग्रहीत कर सकते हैं shared_ptr<void>, और अभी भी सही डिस्ट्रक्टर है अंत, क्योंकि shared_ptrकंस्ट्रक्टर एक फ़ंक्शन टेम्प्लेट है, और डिफ़ॉल्ट रूप से डिलेटर बनाने के लिए पारित वास्तविक ऑब्जेक्ट के प्रकार का उपयोग करेगा:

{
    const shared_ptr<void> sp( new A );
} // calls A::~A() here

बेशक, यह सिर्फ सामान्य void*/ फ़ंक्शन-पॉइंटर प्रकार का क्षरण है, लेकिन बहुत आसानी से पैक किया गया है।


9
संयोग से, मुझे shared_ptr<void>कुछ दिनों पहले एक उदाहरण कार्यान्वयन के साथ मेरे एक मित्र के व्यवहार की व्याख्या करनी थी। :) यह वास्तव में अच्छा है।
Xeo

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

इसलिए, यदि साझा_प्रात एक व्युत्पन्न * को संग्रहीत करता है, लेकिन बेस * ने विध्वंसक को आभासी घोषित नहीं किया, तो साझा किया गया_ साझा करें <void> अभी भी उद्देश्य के रूप में काम करता है, क्योंकि इसके साथ शुरू करने के लिए आधार वर्ग के बारे में भी कभी नहीं पता था। ठंडा!
TamaMcGlinn

@ सहायक: यह करता है, लेकिन unique_ptrटाइप करने वाले को मिटाता नहीं है, इसलिए यदि आप a unique_ptr<T>को असाइन करना चाहते हैं, तो आपको unique_ptr<void>एक स्पष्ट तर्क प्रदान करने की आवश्यकता है, स्पष्ट रूप से, यह जानता है कि कैसे Ta के माध्यम से हटाना है void*। यदि आप अब एक Sभी असाइन करना चाहते हैं, तो आपको एक स्पष्टता की आवश्यकता है, स्पष्ट रूप से, यह जानता है कि कैसे एक के Tमाध्यम से हटाना है void*और एक के Sमाध्यम से भी void*, और , एक void*, जानता है कि यह एक है Tया नहीं S। उस बिंदु पर, आपने के लिए एक प्रकार का मिटाया हुआ डिलेटर लिखा है unique_ptr, और फिर यह भी काम करता है unique_ptr। सिर्फ बॉक्स से बाहर नहीं।
मार्क मुत्ज़ - 13

मुझे लगता है कि आपके द्वारा दिए गए सवाल का जवाब था "मैं इस तथ्य को कैसे हल करूं कि यह काम नहीं करता है unique_ptr?" कुछ लोगों के लिए उपयोगी, लेकिन मेरे सवाल का जवाब नहीं दिया। मुझे लगता है कि जवाब है, क्योंकि साझा किए गए बिंदुओं को मानक पुस्तकालय के विकास में अधिक ध्यान मिला। जो मुझे लगता है कि थोड़ा उदास है क्योंकि अद्वितीय संकेत सरल हैं, इसलिए बुनियादी कार्यक्षमताओं को लागू करना आसान होना चाहिए, और वे अधिक कुशल हैं ताकि लोगों को उनका अधिक उपयोग करना चाहिए। इसके बजाय हमारे पास इसके विपरीत है।
अपोलो ने मोनिका का समर्थन किया

54

मौलिक रूप से, वे आपके विकल्प हैं: वर्चुअल फ़ंक्शन या फ़ंक्शन पॉइंटर्स।

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

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


1
तो, दूसरे शब्दों में मैंने प्रश्न में जो उदाहरण दिए हैं? हालाँकि, इसे इस तरह लिखने के लिए धन्यवाद, विशेष रूप से वर्चुअल फ़ंक्शंस और टाइप-इरेड डेटा पर कई ऑपरेशन के लिए।
Xio

कम से कम 2 अन्य विकल्प हैं। मैं एक उत्तर की रचना कर रहा हूं।
जॉन डिबलिंग

25

मैं भी void*"कच्चे भंडारण" के उपयोग के समान ( ) पर विचार करूंगा char buffer[N]:।

C ++ 0x में आपके पास इसके std::aligned_storage<Size,Align>::typeलिए है।

आप वहां अपनी इच्छानुसार कुछ भी स्टोर कर सकते हैं, जब तक यह काफी छोटा है और आप संरेखण से ठीक से निपटते हैं।


4
खैर, हाँ, Boost.Function वास्तव में इस और मेरे द्वारा दिए गए दूसरे उदाहरण के संयोजन का उपयोग करता है। यदि फ़न्क्टर काफी छोटा है, तो यह फ़नकारक_बफ़र के अंदर आंतरिक रूप से संग्रहीत करता है। std::aligned_storageहालांकि, धन्यवाद के बारे में जानने के लिए अच्छा है ! :)
Xio

आप इसके लिए प्लेसमेंट नए का भी उपयोग कर सकते हैं ।
12

2
@RustyX: वास्तव में, आपको करना होगा। std::aligned_storage<...>::typeएक कच्चा बफर है, जो इसके विपरीत char [sizeof(T)], उपयुक्त रूप से संरेखित है। अपने आप से, हालांकि, यह निष्क्रिय है: यह अपनी स्मृति को आरंभ नहीं करता है, एक वस्तु का निर्माण नहीं करता है, कुछ भी नहीं। इसलिए, एक बार जब आपके पास इस प्रकार का बफर होता है, तो आपको मैन्युअल रूप से इसके अंदर वस्तुओं (या तो प्लेसमेंट newया एक आवंटनकर्ता constructविधि के साथ) का निर्माण करना होगा और आपको मैन्युअल रूप से इसके अंदर वस्तुओं को भी नष्ट करना होगा (या तो अपने विध्वंसक को मैन्युअल रूप से लागू करना होगा या आवंटनकर्ता destroyविधि का उपयोग करना होगा) )।
मथिउ एम।

22

Stroustrup, C ++ प्रोग्रामिंग भाषा (4th संस्करण) में , 25.3 , बताता है:

कई प्रकार के मूल्यों के लिए एकल रन-टाइम प्रतिनिधित्व का उपयोग करने और (स्थिर) प्रकार की प्रणाली पर भरोसा करने की तकनीक के वेरिएंट को यह सुनिश्चित करने के लिए कि उन्हें केवल उनके घोषित प्रकार के अनुसार उपयोग किया जाता है, टाइप इरेज़र कहा जाता है ।

विशेष रूप से, यदि हम टेम्प्लेट का उपयोग करते हैं तो टाइप एस्ट्रैस करने के लिए वर्चुअल फ़ंक्शंस या फ़ंक्शन पॉइंटर्स का कोई उपयोग नहीं किया जाता है। केस, पहले से ही अन्य उत्तर में, सही डिस्ट्रक्टर कॉल के अनुसार, एक में संग्रहीत प्रकार के अनुसार std::shared_ptr<void>इसका एक उदाहरण है।

Stroustrup की पुस्तक में प्रदान किया गया उदाहरण केवल आनंददायक है।

लागू करने के बारे में सोचो template<class T> class Vector, की तर्ज पर एक कंटेनर std::vector। जब आप Vectorबहुत से अलग-अलग पॉइंटर्स प्रकारों के साथ अपने उपयोग करेंगे , जैसा कि अक्सर होता है, तो कंपाइलर प्रत्येक पॉइंटर प्रकार के लिए अलग-अलग कोड उत्पन्न करेगा।

इस कोड ब्लोट को पॉइंटर्स के लिए वेक्टर की विशेषज्ञता को परिभाषित void*करके और फिर इस स्पेशलाइजेशन को Vector<T*>अन्य सभी प्रकारों के लिए एक सामान्य आधार कार्यान्वयन के रूप में उपयोग करके रोका जा सकता है T:

template<typename T>
class Vector<T*> : private Vector<void*>{
// all the dirty work is done once in the base class only 
public:
    // ...
    // static type system ensures that a reference of right type is returned
    T*& operator[](size_t i) { return reinterpret_cast<T*&>(Vector<void*>::operator[](i)); }
};

आप देख सकते हैं, हम एक जोरदार टाइप किया कंटेनर लेकिन है Vector<Animal*>, Vector<Dog*>, Vector<Cat*>, ..., एक ही (सी ++ साझा करेंगे और उनके सूचक प्रकार होने बाइनरी) लागू करने के लिए कोड, मिट के पीछे void*


2
बिना मतलब के ईश निंदा करना: मैं स्ट्रेटस्ट्रुप द्वारा दी गई तकनीक को CRTP पसंद करूंगा।
davidhigh

@davidhigh क्या मतलब है तुम्हारा?
पाओलो एम

एक CRTP बेस क्लास का उपयोग करके एक ही व्यवहार (कम akward सिंटैक्स के साथ) प्राप्त कर सकते हैं template<typename Derived> VectorBase<Derived>जो तब विशेष है template<typename T> VectorBase<Vector<T*> >। इसके अलावा, यह दृष्टिकोण केवल पॉइंटर्स के लिए नहीं, बल्कि किसी भी प्रकार के लिए काम करता है।
davidhigh

3
ध्यान दें कि अच्छा सी ++ लिंकर्स समान तरीकों और कार्यों को मर्ज करते हैं: गोल्ड लिंकर, या एमएसवीसी कॉमडेट तह। कोड उत्पन्न होता है, लेकिन फिर लिंक करने के दौरान छोड़ दिया जाता है।
यक्क - एडम नेवरामोंट

1
@davidhigh मैं आपकी टिप्पणी को समझने की कोशिश कर रहा हूं और आश्चर्य है कि क्या आप मुझे एक लिंक या एक पैटर्न का नाम दे सकते हैं जिसके लिए खोज करना है (सीआरटीपी नहीं है, लेकिन एक तकनीक का नाम जो वर्चुअल फ़ंक्शन या फ़ंक्शन पॉइंटर्स के बिना टाइप इरेज़ेशन की अनुमति देता है) । सम्मान के साथ, - क्रिस
क्रिस चियासन

19

प्रकारों को मिटाने की तकनीकों की एक (काफी कम) सूची और ट्रेड-ऑफ्स के बारे में चर्चा के लिए पदों की इस श्रृंखला को देखें: भाग I , भाग II , भाग III , भाग IV

जिसे मैंने अभी तक उल्लेख नहीं किया है वह Adobe.Poly , और Boost.Variant है , जिसे कुछ हद तक एक प्रकार का क्षरण माना जा सकता है।


7

जैसा कि मार्क ने कहा है, एक कास्ट का उपयोग कर सकता है std::shared_ptr<void>। उदाहरण के लिए , फ़ंक्शन पॉइंटर में टाइप स्टोर करें , इसे कास्ट करें और केवल एक प्रकार के फ़नकार में स्टोर करें:

#include <iostream>
#include <memory>
#include <functional>

using voidFun = void(*)(std::shared_ptr<void>);

template<typename T>
void fun(std::shared_ptr<T> t)
{
    std::cout << *t << std::endl;
}

int main()
{
    std::function<void(std::shared_ptr<void>)> call;

    call = reinterpret_cast<voidFun>(fun<std::string>);
    call(std::make_shared<std::string>("Hi there!"));

    call = reinterpret_cast<voidFun>(fun<int>);
    call(std::make_shared<int>(33));

    call = reinterpret_cast<voidFun>(fun<char>);
    call(std::make_shared<int>(33));


    // Output:,
    // Hi there!
    // 33
    // !
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.