web-dev-qa-db-fra.com

Initialisation d'une variable de type inconnu via des constructeurs surchargés en C ++

venant principalement d'un arrière-plan python), j'ai eu un peu de mal à travailler avec des types en C++.

J'essaie d'initialiser une variable de classe via l'un des constructeurs surchargés qui prennent différents types comme paramètres. J'ai lu que l'utilisation du mot clé auto peut être utilisée pour la déclaration automatique d'une variable, mais dans mon cas, elle ne sera pas initialisée tant qu'un constructeur n'aura pas été choisi. Cependant, le compilateur n'est pas content de ne pas initialiser 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;
    }
};

En python cela pourrait ressembler à:

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

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

Quelle est la bonne façon d'utiliser le mot clé auto dans ce scénario? Dois-je utiliser une approche complètement différente?

22
Tom

Initialisation d'une variable de type inconnu via des constructeurs surchargés en C++

Il n'existe pas de "variable de type inconnu" en C++.

Quelle est la bonne façon d'utiliser le mot clé auto dans ce scénario?

les variables déduites automatiquement ont un type qui est déduit de l'initialiseur. S'il n'y a pas d'initialiseur, vous ne pouvez pas utiliser auto. auto ne peut pas être utilisé pour une variable membre non statique. Une instance d'une classe ne peut pas avoir des membres de type différent d'une autre instance.

Il n'y a aucun moyen d'utiliser le mot-clé auto dans ce scénario.

Dois-je utiliser une approche complètement différente?

Probablement. Il semble que vous essayiez d'implémenter un std::variant. Si vous avez besoin d'une variable pour stocker l'un des X types de types, c'est ce que vous devez utiliser.

Cependant, vous essayez peut-être d'émuler la saisie dynamique en C++. Bien que cela vous soit familier en raison de votre expérience avec Python, dans de nombreux cas, ce n'est pas l'approche idéale. Par exemple, dans cet exemple de programme particulier, tout ce que vous faites avec la variable membre est de l'imprimer. Il serait donc plus simple de stocker une chaîne dans chaque cas. D'autres approches sont polymorphisme statique comme indiqué par Rhathin ou OOP style polymorphisme dynamique comme indiqué par Fire Lancer.

17
eerorika

C++ est un langage à typage statique , ce qui signifie que tous les types de variables sont déterminés avant l'exécution. Par conséquent, le mot clé auto n'est pas quelque chose comme le mot clé var en javascript, qui est un langage typé dynamiquement. auto mot-clé est couramment utilisé pour spécifier des types qui sont inutilement complexes.

Ce que vous recherchez peut être fait à l'aide de la classe de modèle C++ à la place, ce qui permet de créer plusieurs versions de la classe qui prennent différents types.

Ce code pourrait être la réponse que vous recherchez.

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

Ce code se compilerait si certaines conditions sont remplies, comme la fonctionoperator<< doit être défini pour std :: ostream & et type T.

11
KimHajun

Une approche différente de celle que d'autres ont proposée consiste à utiliser des modèles. Voici un exemple:

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

Ensuite, vous pouvez utiliser votre classe comme ceci:

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

Vous pouvez utiliser le type std::variant. Le code ci-dessous montre une façon (mais c'est un peu maladroit, je dois admettre):

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

Il serait bien plus agréable d'écrire la std::get<0>(value) sous la forme std::get<value.index()>(value) mais, hélas, le "x" dans <x> Doit être une expression constante à la compilation.

3
Adrian Mole

auto doit être déductible à un type spécifique, il ne fournit pas de typage dynamique à l'exécution.

Si au moment de déclarer Token vous connaissez tous les types possibles, vous pouvez utiliser std::variant<Type1, Type2, Type3> etc. Cela revient à avoir un "type enum" et une "union". Il s'assure que les constructeurs et destructeurs appropriés sont appelés.

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

Une alternative pourrait être de créer un sous-type Token différent pour chaque cas (en utilisant éventuellement des modèles) avec des méthodes virtuelles appropriées.

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;
    }
}
1
Fire Lancer

La solution ci-dessous est similaire dans son esprit à celle de la réponse de Fire Lancer. Sa principale différence est qu'il suit le commentaire en utilisant éventuellement des modèles , et supprime ainsi la nécessité de créer explicitement des instances dérivées de l'interface. Token n'est pas lui-même la classe d'interface. Au lieu de cela, il définit l'interface comme une classe interne et une classe de modèle interne pour automatiser la définition des classes dérivées.

Sa définition semble trop complexe. Cependant, Token::Base définit l'interface et Token::Impl<> dérive de l'interface. Ces classes internes sont entièrement cachées à l'utilisateur de Token. L'utilisation ressemblerait à:

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

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

De plus, la solution ci-dessous illustre comment on pourrait implémenter un opérateur de conversion pour affecter une instance Token à une variable régulière. Il repose sur dynamic_cast, et lèvera une exception si le transtypage est invalide.

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

La définition de Token est ci-dessous.

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

Essayez-le en ligne!

0
jxh