2009-06-18 8 views
10

Je suis sûr que c'est une question très simple. Le code suivant montre ce que je suis en train de faire:Bonne façon d'initialiser de manière conditionnelle une variable membre C++?

class MemberClass { 
public: 
    MemberClass(int abc){ } 
}; 

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) { 
     if(xyz == 42) 
      m_class = MemberClass(12); 
     else 
      m_class = MemberClass(32); 
    } 
}; 

Cela ne compile pas, parce que m_class est en cours de création avec un constructeur vide (qui n'existe pas). Quelle est la bonne façon de faire cela? Ma conjecture utilise des pointeurs et instanciant m_class en utilisant new, mais j'espère qu'il y a un moyen plus facile.

Editer: J'aurais dû le dire plus tôt, mais mon problème actuel a une complication supplémentaire: J'ai besoin d'appeler une méthode avant d'initialiser m_class, afin de configurer l'environnement. Donc:

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) { 
     do_something(); // this must happen before m_class is created 
     if(xyz == 42) 
      m_class = MemberClass(12); 
     else 
      m_class = MemberClass(32); 
    } 
}; 

Est-il possible d'atteindre cet objectif avec des astuces de liste d'initialisation?

Répondre

24

Utilisez l'opérateur conditionnel. Si l'expression est plus grande, utilisez une fonction

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(xyz == 42 ? 12 : 32) { 

    } 
}; 

class MyClass { 
    static int classInit(int n) { ... } 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(classInit(xyz)) { 

    } 
}; 

Pour appeler une fonction avant d'initialiser m_class, vous pouvez placer un struct avant que membre et levier RAII

class MyClass { 
    static int classInit(int n) { ... } 
    struct EnvironmentInitializer { 
     EnvironmentInitializer() { 
      do_something(); 
     } 
    } env_initializer; 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(classInit(xyz)) { 

    } 
}; 

Ceci appellera do_something() avant d'initialiser m_class. Notez que vous n'êtes pas autorisé à appeler les fonctions membres non statiques de MyClass avant que la liste d'initialisation du constructeur ne soit terminée. La fonction doit être un membre de sa classe de base et la classe de base 'ctor doit déjà être complétée pour que cela fonctionne.

Notez également que la fonction, bien sûr, est toujours appelée, pour chaque objet séparé créé - pas seulement pour le premier objet créé. Si vous voulez faire cela, vous pouvez créer une variable statique dans le constructeur de l'initialiseur:

class MyClass { 
    static int classInit(int n) { ... } 
    struct EnvironmentInitializer { 
     EnvironmentInitializer() { 
      static int only_once = (do_something(), 0); 
     } 
    } env_initializer; 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(classInit(xyz)) { 

    } 
}; 

Il utilise l'opérateur virgule. Notez que vous pouvez prendre toute exception lancée par do_something en utilisant une fonction essayez de bloquer

class MyClass { 
    static int classInit(int n) { ... } 
    struct EnvironmentInitializer { 
     EnvironmentInitializer() { 
      static int only_once = (do_something(), 0); 
     } 
    } env_initializer; 
public: 
    MemberClass m_class; 
    MyClass(int xyz) try : m_class(classInit(xyz)) { 

    } catch(...) { /* handle exception */ } 
}; 

La fonction do_something sera appelé à nouveau la prochaine fois, si elle a lancé cette exception qui a causé l'objet MyClass ne parviennent pas à créer.Hope this helps :)

+0

Merci! J'ai mis à jour ma question - J'ai en fait besoin d'exécuter une méthode avant de créer m_class. Est-ce possible? Peut-être que je pourrais le faire dans "classInit", mais ce ne serait pas très élégant. –

+0

J'ai beaucoup appris, merci! :-) –

+0

Quand j'ai vu l'utilisation de l'opérateur de virgule, je savais que la personne répondant à cette question était intelligente. Quand j'ai vu l'utilisation de la fonction try block, je savais que la personne répondant à cette question était litb. Quand j'ai vu la personne répondre à cette question était litb ... eh bien je suppose que je savais déjà que c'était lui :) – mmocny

5

Utilisez la syntaxe de liste initialiseur:

class MyClass { 
public: 
    MemberClass m_class; 
    MyClass(int xyz) : m_class(xyz == 42 ? MemberClass(12) : MemberClass(32) 
           /* see the comments, cleaner as xyz == 42 ? 12 : 32*/) 
    { } 
}; 

Probablement plus propre avec une usine:

MemberClass create_member(int x){ 
    if(xyz == 42) 
    return MemberClass(12); 
    // ... 
} 

//... 
MyClass(int xyz) : m_class(create_member(xyz)) 
+2

Vous créez un objet sans nom, puis en utilisant le copier-ctor pour initialiser le membre, plutôt que de l'initialiser directement. –

+0

Oui, en tapant trop vite pour mon propre bien. Personnellement, je déplacerais toujours la logique dans MemberClass ou une usine. –

5
MyClass(int xyz) : m_class(xyz==42 ? 12 : 32) {} 

Pour répondre à votre question révisée, qui deviennent un peu délicat. Le moyen le plus simple serait de faire m_class un pointeur. Si vous le voulez vraiment en tant que membre de données, alors vous devez être créatif. Créez une nouvelle classe (il est préférable qu'elle soit interne à MyClass). C'est ctor être la fonction qui doit être appelée. Incluez-le en premier parmi les déclarations des membres de données (cela le rendra le premier instaniated).

class MyClass 
{ 
    class initer { public: initer() { 
        // this must happen before m_class is created 
        do_something();       
        } 
        } 

    initer  dummy; 
public: 

    MemberClass m_class; 
    MyClass(int xyz) : m_class(xyz==42? 12 : 43) 
    { 
     // dummy silently default ctor'ed before m_class. 
    } 
}; 
0

Ou:

class MemberClass { 
public: 
    MemberClass(int abc){ } 
}; 

class MyClass { 
public: 
    MemberClass* m_class; 
    MyClass(int xyz) { 
     if(xyz == 42) 
      m_class = new MemberClass(12); 
     else 
      m_class = new MemberClass(32); 
    } 
}; 

Si vous voulez en quelque sorte toujours garder la même syntaxe. L'initalisation des membres est cependant plus efficace.

0

Essayez ceci:

class MemberClass 
{ 
public:  
    MemberClass(int abc = 0){ } 
}; 

Cela lui donne une valeur par défaut, et votre constructeur par défaut.

+1

Il existe parfois des raisons de ne pas avoir de constructeur par défaut. –

+0

Vous devez avoir un paramètre par défaut qui le place dans un état zombie connu. Pas idéal mais parfois le seul moyen. –

+0

Parfois, il y a des raisons de rendre le constructeur par défaut privé, mais il devrait toujours être là ... – Kieveli

0

Pour avoir l'initialisation se produit après d'autres choses qui se passe, vous avez besoin en effet d'utiliser des pointeurs, quelque chose comme ceci:

class MyClass { 
public: 
    MemberClass * m_pClass; 
    MyClass(int xyz) { 
     do_something(); // this must happen before m_class is created 
     if(xyz == 42) 
      m_pClass = new MemberClass(12); 
     else 
      m_pClass = new MemberClass(32); 
    } 
}; 

La seule différence est que vous aurez besoin d'accéder à des variables membres comme m_pClass->counter au lieu de m_class.counter et delete m_pClass dans le destructeur.

+0

L'alternative est d'initialiser Myclass dans un état de zombie factice et ré-init plus tard une fois que vous connaissez la valeur. Mais je préférerais des pointeurs. –

Questions connexes