C ++ 'da aşırı yüklenmiş yapıcılar aracılığıyla bilinmeyen bir değişkenin başlatılması


22

öncelikle Python arka plan geliyor C ++ türleri ile çalışma ile biraz mücadele var.

Bir parametre değişkenini farklı türlerde parametre olarak alan birkaç aşırı yüklenmiş kuruculardan biri aracılığıyla başlatmaya çalışıyorum. autoAnahtar kelimeyi kullanmanın bir değişkenin otomatik bildirimi için kullanılabileceğini okudum , ancak benim durumumda bir kurucu seçilene kadar başlatılmayacak. Ancak derleyici başlatmama konusunda mutlu değildir 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;
    }
};

Python'da bu şöyle görünebilir:

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

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

autoBu senaryoda anahtar kelimeyi kullanmanın doğru yolu nedir ? Tamamen farklı bir yaklaşım kullanmalı mıyım?


2
autoSınıf üyeleri için hiç kullanamayacağınıza inanıyorum. Alakalı fakat güncel olmayan soru: “Otomatik” üye değişkenine sahip olmak mümkün mü?
Yksisarvinen

Şablon kullanmamak için bir neden var mı?
Jimmy RT

Python ile, türler çalışma zamanında her işlemde belirlenir - bu ek yük gerektirir, ancak değişken türlerinin bir ifadeden diğerine değişmesine izin verir. C ++ 'da türlerin önceden bilinmesi gerekir, böylece kod derlenebilir - float ve int farklı ikili düzenlere sahiptir ve çalışmak için farklı montaj talimatları gerektirir. Çalışma zamanında esneklik istiyorsanız, performans yükü ekleyerek her tür için geçerli kod içeren birçok daldan birini seçen varyant gibi bir birleşim türü kullanmanız gerekir. İnt ve float sürümlerini ayrı tutmak istiyorsanız, şablonlar arkadaşınızdır.
Jake

Yanıtlar:


17

C ++ 'da aşırı yüklenmiş yapıcılar aracılığıyla bilinmeyen bir değişkenin başlatılması

C ++ 'da "bilinmeyen tipte değişken" diye bir şey yoktur.

Bu senaryoda otomatik anahtar kelimeyi kullanmanın doğru yolu nedir?

otomatik olarak çıkarılan değişkenlerin, başlatıcıdan çıkarılan bir türü vardır. Başlatıcı yoksa otomatik kullanamazsınız. auto, statik olmayan bir üye değişken için kullanılamaz. Sınıfın bir örneğinin başka bir örneğe göre farklı yazılan üyeleri olamaz.

Bu senaryoda otomatik anahtar kelime kullanmanın bir yolu yoktur.

Tamamen farklı bir yaklaşım kullanmalı mıyım?

Muhtemelen. Görünüşe göre bir std::variant. X sayısından birini saklamak için bir değişkene ihtiyacınız varsa, bunu kullanmalısınız.

Ancak, C ++ dinamik yazmayı taklit etmeye çalışıyor olabilirsiniz. Python deneyiminden dolayı size tanıdık gelse de, çoğu durumda bu ideal bir yaklaşım değildir. Örneğin, bu özel örnek programda, üye değişkeni ile yaptığınız tek şey onu yazdırmaktır. Bu nedenle, her durumda bir dize saklamak daha kolay olurdu. Diğer yaklaşımlar, Rhathin tarafından gösterilen statik polimorfizm veya Fire Lancer tarafından gösterildiği gibi OOP tarzı dinamik polimorfizmdir .


Birlik kullanımı da bu durumda geçerli olur mu?
wondra

unionhataya eğilimli düşük seviyeli bir mekanizmadır. variantmuhtemelen dahili olarak kullanır ve kullanımını daha güvenli hale getirir.
Erlkoenig

@wondra sendikası tek başına şu anda aktif olan üye için denetlenemeyeceği için çok yararlı olmaz. Std :: string gibi önemsiz olmayan sınıflarla (özel yıkıcıya sahip) kullanmak da çok acı vericidir. Birinin istediği etiketli bir birliktir. Std :: varyantının uyguladığı veri yapısı hangisidir.
eerorika

1
libstdc ++ 'ın variant yaptığı kullanımını union. Ham bellek ve yeni yerleşim kullanan alternatif, bir constexpryapıcıda kullanılamaz .
Erlkoenig

@Erlkoenig yeterince adil, söylediklerimi geri alıyorum. Sadece sendika kullanmayan destek uygulamalarına baktım ve herkesin aynı şeyi yaptığını varsaydım.
eerorika

11

C ++ statik olarak yazılan bir dildir , yani tüm değişken türlerinin çalışma zamanından önce belirlendiği anlamına gelir. Bu nedenle, autoanahtar varkelime dinamik olarak yazılmış bir dil olan javascript'teki anahtar kelime gibi bir şey değildir . autoanahtar kelime, genellikle karmaşık olmayan türleri belirtmek için kullanılır.

Aradığınız şey, bunun yerine C ++ şablon sınıfını kullanarak yapılabilir; bu, sınıfın farklı türler alan birden çok sürümünü oluşturmaya izin verir.

Bu kod aradığınız cevap olabilir.

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;
    }
};

Bu kod, bazı koşullar yerine getirilirse derlenir; örneğin, operator<<std :: ostream & ve T tipi için işlev tanımlanmalıdır.


6

Diğerlerinin önerdiklerinden farklı bir yaklaşım, şablon kullanmaktır. İşte bir örnek:

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;
    }
};

Sonra sınıfınızı şu şekilde kullanabilirsiniz:

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

3

std::variantTürü kullanabilirsiniz . Aşağıdaki kod bir yolu gösterir (ama biraz beceriksiz, itiraf etmeliyim):

#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;
}

Bu ise çok daha güzel olurdu std::get<0>(value)olarak yazılmış olabilir std::get<value.index()>(value), ne yazık ki ancak "x" in <x>bir derleme zamanı sabiti ifadesi olmak zorunda.


1
Muhtemelen daha iyi kullanmak std::visityerine switch.
eerorika

1

auto belirli bir türden türetilebilir olmalıdır, çalışma zamanı dinamik yazımı sağlamaz.

Bildiri sırasında Tokenkullanabileceğiniz tüm olası türleri std::variant<Type1, Type2, Type3>vb. Biliyorsanız . Bu, "tip enum" ve "union" a benzer. Uygun kurucuların ve yıkıcıların çağrılmasını sağlar.

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

Bir alternatif Token, uygun sanal yöntemlerle her vaka için (muhtemelen şablonlar kullanarak) farklı bir alt tip oluşturmak olabilir .

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

Aşağıdaki çözüm ruh olarak Fire Lancer'ın cevabındaki ile benzerdir. Buradaki önemli fark, muhtemelen şablonlar kullanan yorumu izlemesi ve böylece arayüzün açıkça oluşturulmuş türetilmiş örneklerine olan ihtiyacı ortadan kaldırmasıdır. Tokenkendisi arayüz sınıfı değildir. Bunun yerine, türetilmiş sınıfların tanımını otomatikleştirmek için arabirimi bir iç sınıf ve bir iç şablon sınıfı olarak tanımlar.

Tanımı aşırı karmaşık görünüyor. Ancak, Token::Basearabirimi tanımlar ve arabirimden Token::Impl<>türetilir. Bu iç sınıflar tamamen kullanıcısına gizlidir Token. Kullanım şöyle görünecektir:

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

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

Ayrıca, aşağıdaki çözüm, Tokennormal bir değişkene örnek atamak için bir dönüşüm operatörünün nasıl uygulanabileceğini gösterir . Bu dynamic_cast, oyuncu kadrosu geçersizse bir istisna atar.

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

Tanımı Tokenaşağıdadır.

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);
    }
};

Çevrimiçi deneyin!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.