Inizializzazione di una variabile di tipo sconosciuto tramite costruttori sovraccarichi in C ++


22

provenendo principalmente da un background di Python, ho avuto delle difficoltà a lavorare con i tipi in C ++.

Sto tentando di inizializzare una variabile di classe tramite uno dei numerosi costruttori sovraccarichi che prendono diversi tipi come parametri. Ho letto che l'uso della autoparola chiave può essere utilizzato per la dichiarazione automatica di una variabile, tuttavia nel mio caso non verrà inizializzato fino a quando non viene scelto un costruttore. Tuttavia, il compilatore non è contento di non inizializzare 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;
    }
};

In Python questo potrebbe apparire come:

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

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

Qual è il modo giusto di usare la autoparola chiave in questo scenario? Dovrei usare un approccio completamente diverso?


2
Credo che non si possa usare autoper i membri della classe? Domanda pertinente ma obsoleta: è possibile avere una variabile membro "auto"?
Yksisarvinen,

Qualche motivo per non usare i modelli?
Jimmy RT,

Con Python, i tipi sono determinati su ogni operazione in fase di esecuzione - ciò richiede un sovraccarico, ma consente ai tipi di variabili di passare da un'istruzione alla successiva. In C ++ i tipi devono essere conosciuti in anticipo, in modo che il codice possa essere compilato - float e int abbiano layout binari diversi e richiedono diverse istruzioni di assemblaggio con cui lavorare. Se si desidera flessibilità in fase di esecuzione, è necessario utilizzare un tipo di unione come la variante che sceglie uno dei numerosi rami contenenti codice valido per ciascun tipo, aggiungendo un sovraccarico di prestazioni. Se vuoi mantenere separate le versioni int e float, i template sono i tuoi amici.
Jake,

Risposte:


17

Inizializzazione di una variabile di tipo sconosciuto tramite costruttori sovraccarichi in C ++

Non esiste una "variabile di tipo sconosciuto" in C ++.

Qual è il modo giusto di utilizzare la parola chiave auto in questo scenario?

le variabili auto dedotte hanno un tipo che viene dedotto dall'inizializzatore. Se non è presente un inizializzatore, non è possibile utilizzare auto. auto non può essere utilizzato per una variabile membro non statica. Un'istanza di una classe non può avere membri tipizzati in modo diverso rispetto a un'altra istanza.

Non è possibile utilizzare la parola chiave automatica in questo scenario.

Dovrei usare un approccio completamente diverso?

Probabilmente. Sembra che tu stia cercando di implementare a std::variant. Se hai bisogno di una variabile per memorizzare un numero X di tipi, è quello che dovresti usare.

Tuttavia, potresti provare a emulare la digitazione dinamica in C ++. Sebbene possa esserti familiare a causa dell'esperienza con Python, in molti casi questo non è l'approccio ideale. Ad esempio, in questo particolare programma di esempio, tutto ciò che fai con la variabile membro è stamparlo. Quindi sarebbe più semplice memorizzare una stringa in ciascun caso. Altri approcci sono il polimorfismo statico come mostrato da Rhathin o il polimorfismo dinamico in stile OOP come mostrato da Fire Lancer.


Anche l'utilizzo del sindacato si qualificherebbe in questo caso?
Wondra

unionè un meccanismo di basso livello soggetto a errori. variantprobabilmente lo utilizza internamente e ne rende l'utilizzo più sicuro.
Erlkoenig,

L'unione @wondra da sola non sarebbe molto utile poiché non può essere ispezionata per quale membro è attualmente attivo. È anche molto doloroso usare con classi non banali (che hanno un distruttore personalizzato) come std :: string. Ciò che si vorrebbe è un'unione contrassegnata . Qual è la struttura dati che implementa std :: variant.
Eerorika,

1
libstdc ++ 's variant fa uso union. L'alternativa, utilizzando la memoria grezza e il posizionamento nuovo, non può essere utilizzata in un constexprcostruttore.
Erlkoenig,

@Erlkoenig abbastanza giusto, riprendo ciò che ho detto. Avevo solo considerato l'implementazione dei boost, che non utilizzava il sindacato, e supponevo che tutti facessero lo stesso.
Eerorika,

11

C ++ è un linguaggio tipicamente statico , il che significa che tutti i tipi di variabili sono determinati prima del runtime. Pertanto, la autoparola chiave non è qualcosa come la varparola chiave in javascript, che è un linguaggio tipizzato in modo dinamico. autola parola chiave viene comunemente utilizzata per specificare tipi inutilmente complessi.

Quello che stai cercando potrebbe essere fatto utilizzando invece la classe modello C ++, che consente di creare più versioni della classe che accetta tipi diversi.

Questo codice potrebbe essere la risposta che stai cercando.

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

Questo codice verrebbe compilato se fossero soddisfatte alcune condizioni, come la funzione operator<<dovrebbe essere definita per std :: ostream & e tipo T.


6

Un approccio diverso rispetto a quello che altri hanno proposto è quello di utilizzare i modelli. Ecco un esempio:

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

Quindi puoi usare la tua classe in questo modo:

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

3

Puoi usare il std::varianttipo. Il codice qui sotto mostra un modo (ma è un po 'goffo, devo ammetterlo):

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

Sarebbe molto più bello se si std::get<0>(value)potesse scrivere come std::get<value.index()>(value)ma, ahimè, la "x" in <x>deve essere un'espressione costante in fase di compilazione.


1
Probabilmente meglio usare al std::visitposto di switch.
Eerorika,

1

auto deve essere deducibile da un tipo specifico, non fornisce la digitazione dinamica di runtime.

Se al momento della dichiarazione Tokenconosci tutti i tipi possibili che puoi usare std::variant<Type1, Type2, Type3>ecc. Questo è simile ad avere un "tipo enum" e un "unione". Si assicura che vengano chiamati costruttori e distruttori adeguati.

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

Un'alternativa potrebbe essere quella di creare un Tokensottotipo diverso per ogni caso (possibilmente utilizzando modelli) con metodi virtuali adeguati.

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

La soluzione seguente è simile nello spirito a quella nella risposta di Fire Lancer. La differenza fondamentale è che segue il commento eventualmente utilizzando i modelli e quindi rimuove la necessità di creare istanze derivate esplicite dell'interfaccia. Tokennon è essa stessa la classe di interfaccia. Al contrario, definisce l'interfaccia come una classe interna e una classe modello interna per automatizzare la definizione delle classi derivate.

La sua definizione appare eccessivamente complessa. Tuttavia, Token::Basedefinisce l'interfaccia e Token::Impl<>deriva dall'interfaccia. Queste classi interne sono interamente nascoste all'utente di Token. L'utilizzo sarebbe simile a:

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

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

Inoltre, la soluzione seguente illustra come si potrebbe implementare un operatore di conversione per assegnare Tokenun'istanza a una variabile regolare. Si basa su dynamic_caste genererà un'eccezione se il cast non è valido.

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

La definizione di Tokenè sotto.

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

Provalo online!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.