C ++ में ओवरलोडेड कंस्ट्रक्टर्स के माध्यम से अज्ञात प्रकार के एक वैरिएबल की शुरुआत


22

मुख्य रूप से अजगर की पृष्ठभूमि से आने वाले मैं सी ++ में प्रकारों के साथ काम करने में कुछ संघर्ष कर रहा हूं।

मैं कई अतिभारित निर्माणकर्ताओं में से एक के माध्यम से एक वर्ग चर को इनिशियलाइज़ करने का प्रयास कर रहा हूं जो विभिन्न प्रकार के मापदंडों के रूप में लेते हैं। मैंने पढ़ा है कि autoकीवर्ड का उपयोग किसी वैरिएबल के ऑटो डिक्लेरेशन के लिए किया जा सकता है, हालाँकि मेरे मामले में इसे तब तक इनिशियलाइज़ नहीं किया जाएगा जब तक कि कंस्ट्रक्टर को चुना नहीं जाता। हालांकि संकलक शुरू नहीं होने के बारे में खुश नहीं है value

class Token {
public:

    auto value;

    Token(int ivalue) {
        value = ivalue;
    }
    Token(float fvalue) {
        value = fvalue;
    }
    Token(std::string svalue) {
        value = svalue;
    }

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

अजगर में यह लग सकता है:

class Token():
        def __init__(self, value):
             self.value = value

        def printValue(self):
             print("The token value is: %s" % self.value)

autoइस परिदृश्य में कीवर्ड का उपयोग करने का सही तरीका क्या है ? क्या मुझे पूरी तरह से एक अलग दृष्टिकोण का उपयोग करना चाहिए?


2
मेरा मानना ​​है कि आप autoकक्षा के सदस्यों के लिए बिल्कुल भी उपयोग नहीं कर सकते हैं ? प्रासंगिक लेकिन पुराना प्रश्न: क्या "ऑटो" सदस्य चर होना संभव है?
युकिसार्विनें

कोई भी कारण टेम्पलेट का उपयोग नहीं करना है?
जिमी आरटी

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

जवाबों:


17

C ++ में ओवरलोडेड कंस्ट्रक्टर्स के माध्यम से अज्ञात प्रकार के एक वैरिएबल की शुरुआत

C ++ में "अज्ञात प्रकार का चर" जैसी कोई चीज नहीं है।

इस परिदृश्य में ऑटो कीवर्ड का उपयोग करने का सही तरीका क्या है?

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

इस परिदृश्य में ऑटो कीवर्ड का उपयोग करने का कोई तरीका नहीं है।

क्या मुझे एक अलग दृष्टिकोण का उपयोग करना चाहिए?

शायद। ऐसा लगता है कि आप एक को लागू करने की कोशिश कर रहे हैं std::variant। यदि आपको X संख्या के एक प्रकार को संग्रहीत करने के लिए एक चर की आवश्यकता है, तो आपको वही उपयोग करना चाहिए।

हालाँकि, आप C ++ में डायनामिक टाइपिंग का अनुकरण करने की कोशिश कर रहे होंगे। जबकि यह कई मामलों में आपके लिए परिचित हो सकता है, कई मामलों में आदर्श दृष्टिकोण नहीं है। उदाहरण के लिए, इस विशेष उदाहरण के कार्यक्रम में, सदस्य चर के साथ आप जो भी करते हैं वह सब प्रिंट होता है। इसलिए प्रत्येक मामले में एक स्ट्रिंग को स्टोर करना सरल होगा। अन्य दृष्टिकोण स्थिर बहुरूपता हैं जैसा कि रथिन या ओओपी शैली गतिशील बहुरूपता द्वारा दिखाया गया है जैसा कि फायर लांसर द्वारा दिखाया गया है।


क्या संघ का उपयोग इस मामले में भी योग्य होगा?
वंद्रा

unionएक त्रुटि-प्रवण निम्न-स्तरीय तंत्र है। variantशायद आंतरिक रूप से इसका उपयोग करता है और इसके उपयोग को सुरक्षित बनाता है।
Erlkoenig

@wondra Union अपने आप में बहुत उपयोगी नहीं होगा क्योंकि इसका निरीक्षण नहीं किया जा सकता है कि कौन सा सदस्य वर्तमान में सक्रिय है। नॉन ट्रिवियल क्लासेस (जिसमें कस्टम डिस्ट्रक्टर है) जैसे कि std :: string के साथ उपयोग करना भी बहुत दर्दनाक है। जो चाहेगा वह टैगेड यूनियन होगा। कौन सा डेटास्ट्रक्चर है जो std :: variant लागू करता है।
एरोरिका

1
libstdc ++ का उपयोग variant करता है union। विकल्प, कच्ची मेमोरी और प्लेसमेंट नए का उपयोग करके, एक constexprकंस्ट्रक्टर में उपयोग नहीं किया जा सकता है ।
एर्कोलेनिग

@Erlkoenig मेला काफी, मैंने जो कहा, उसे वापस लेता हूं। मैंने केवल कार्यान्वयन को बढ़ावा देने पर ध्यान दिया था, जो संघ का उपयोग नहीं करता था, और यह मान लेता था कि सभी ने ऐसा ही किया है।
एरोरिका

11

C ++ एक सांख्यिकीय रूप से टाइप की गई भाषा है , जिसका अर्थ है कि सभी चर प्रकार रनटाइम से पहले निर्धारित किए जाते हैं। इसलिए, autoकीवर्ड varजावास्क्रिप्ट में कीवर्ड जैसा कुछ नहीं है , जो एक गतिशील रूप से टाइप की गई भाषा है। autoकीवर्ड का उपयोग आमतौर पर उन प्रकारों को निर्दिष्ट करने के लिए किया जाता है जो अनावश्यक रूप से जटिल हैं।

आप C ++ टेम्प्लेट क्लास के बजाय इसका उपयोग कर सकते हैं, जो विभिन्न प्रकारों को लेने वाले क्लास के कई संस्करण बनाने की अनुमति देता है।

यह कोड आपके द्वारा खोजे जा रहे उत्तर हो सकता है।

template <typename T>
class Token {
private:
    T value;

public:
    Token(const T& ivalue) {
        value = ivalue;
    }

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

यह कोड संकलित करेगा यदि कुछ शर्तों को पूरा किया जाता है, जैसे फ़ंक्शन operator<<को std :: ostream और प्रकार T के लिए परिभाषित किया जाना चाहिए।


6

अन्य लोगों ने जो प्रस्ताव दिया है, उससे अलग दृष्टिकोण, टेम्प्लेट का उपयोग करना है। यहाँ एक उदाहरण है:

template<class T>
class Token {
public:

    T value;

    Token(T value) :
        value(std::move(value))
    {}

    void printValue() {
        std::cout << "The token value is: " << value << std::endl;
    }
};

तो आप इस तरह अपनी कक्षा का उपयोग कर सकते हैं:

Token<int> x(5);
x.printValue();

3

आप std::variantप्रकार का उपयोग कर सकते हैं । नीचे दिया गया कोड एक तरह से दिखाता है (लेकिन यह थोड़ा अनाड़ी है, मुझे मानना ​​होगा):

#include <iostream>
#include <variant>

class Token {
public:

    std::variant<int, float, std::string> value;

    Token(int ivalue) {
        value = ivalue;
    }
    Token(float fvalue) {
        value = fvalue;
    }
    Token(std::string svalue) {
        value = svalue;
    }

    void printValue() {
        switch (value.index()) {
            case 0:
                std::cout << "The token value is: " << std::get<0>(value) << std::endl;
                break;
            case 1:
                std::cout << "The token value is: " << std::get<1>(value) << std::endl;
                break;
            case 2:
                std::cout << "The token value is: " << std::get<2>(value) << std::endl;
                break;
        }
    }
};

int main() {
    Token it(1);
    Token ft(2.2f);
    Token st("three");
    it.printValue();
    ft.printValue();
    st.printValue();
    return 0;
}

यह बहुत अच्छा होगा यदि std::get<0>(value)लिखा जा सकता है std::get<value.index()>(value)लेकिन, अफसोस, "x" को <x>एक संकलन-समय स्थिर अभिव्यक्ति होना चाहिए।


1
संभवतः std::visitइसके बजाय उपयोग करने के लिए बेहतर है switch
एरोरिका

1

auto एक विशिष्ट प्रकार के लिए समर्पण होना चाहिए, यह रनटाइम गतिशील टाइपिंग प्रदान नहीं करता है।

यदि घोषित करने के समय Tokenआप उन सभी संभावित प्रकारों को जानते हैं जिनका आप उपयोग कर सकते हैं std::variant<Type1, Type2, Type3>आदि। यह "टाइप एनम" और "यूनियन" होने के समान है। यह सुनिश्चित करता है कि उचित निर्माणकर्ता और विध्वंसक कहलाते हैं।

std::variant<int, std::string> v;
v = "example";
v.index(); // 1, a int would be 0
std::holds_alternative<std::string>(v); // true
std::holds_alternative<int>(v); // false
std::get<std::string>(v); // "example"
std::get<int>(v); // throws std::bad_variant_access

एक विकल्प Tokenउपयुक्त वर्चुअल विधियों के साथ प्रत्येक मामले (संभवत: टेम्पलेट्स का उपयोग करके) के लिए एक अलग उपप्रकार बनाने के लिए हो सकता है ।

class Token {
public:
    virtual void printValue()=0;
};

class IntToken : public Token {
public:
    int value;
    IntToken(int ivalue) {
        value = ivalue;
    }
    virtual void printValue()override
    {
        std::cout << "The token value is: " << value << std::endl;
    }
}

0

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

यह परिभाषा अत्यधिक जटिल प्रतीत होती है। हालाँकि, Token::Baseइंटरफ़ेस को परिभाषित करता है, और Token::Impl<>इंटरफ़ेस से निकलता है। ये आंतरिक वर्ग पूरी तरह से उपयोगकर्ता के लिए छिपे हुए हैं Token। उपयोग की तरह दिखेगा:

Token s = std::string("hello");
Token i = 7;

std::cout << "The token value is: " << s << '\n';
std::cout << "The token value is: " << i << '\n';

इसके अलावा, नीचे दिए गए समाधान से पता चलता है कि Tokenएक नियमित रूप से चर को उदाहरण देने के लिए कोई रूपांतरण ऑपरेटर कैसे लागू कर सकता है । यह निर्भर करता है dynamic_cast, और यदि अपवाद अमान्य है, तो एक अपवाद को फेंक देगा।

int j = i; // Allowed
int k = s; // Throws std::bad_cast

Tokenनीचे की परिभाषा है।

class Token {

    struct Base {
        virtual ~Base () = default;
        virtual std::ostream & output (std::ostream &os) = 0;
    };

    template <typename T>
    struct Impl : Base {
        T val_;
        Impl (T v) : val_(v) {}
        operator T () { return val_; }
        std::ostream & output (std::ostream &os) { return os << val_; }
    };

    mutable std::unique_ptr<Base> impl_;

public:

    template <typename T>
    Token (T v) : impl_(std::make_unique<Impl<T>>(v)) {}

    template <typename T>
    operator T () const { return dynamic_cast<Impl<T>&>(*impl_); }

    friend auto & operator << (std::ostream &os, const Token &t) {
        return t.impl_->output(os);
    }
};

इसे ऑनलाइन आज़माएं!

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