2010-03-09 8 views
59

J'ai une crampe cérébrale ... comment initialiser correctement un tableau d'objets en C++?C++: initialiseur de constructeur pour les tableaux

exemple non-tableau:

struct Foo { Foo(int x) { /* ... */ } }; 

struct Bar { 
    Foo foo; 

    Bar() : foo(4) {} 
}; 

exemple de tableau:

struct Foo { Foo(int x) { /* ... */ } }; 

struct Baz { 
    Foo foo[3]; 

    // ??? I know the following syntax is wrong, but what's correct? 
    Baz() : foo[0](4), foo[1](5), foo[2](6) {} 
}; 

modifier: idées sauvages & de contournement fous sont appréciés, mais ils ne me aider dans mon cas. Je travaille sur un processeur embarqué où std :: vector et d'autres constructions STL ne sont pas disponibles, et la solution évidente consiste à faire un constructeur par défaut et avoir une méthode explicite init() qui peut être appelée après la construction-temps, de sorte que je ne N'utilisez pas d'initialiseurs du tout. (C'est l'un de ces cas où j'ai été gâté par le mot-clé final de Java + la flexibilité avec les constructeurs.)

+0

Vos classes ne sont pas parce que tout est à bâtir 'private'. –

+5

(accès aux mots-clés laissés pour la simplicité pédagogique) –

+7

Ne serait-il pas plus facile d'utiliser 'struct' à la place de' class' pour la simplicité pédagogique? Je trouve le code qui compile plus facile à apprendre à partir de ;-) –

Répondre

48

Il n'y a aucun moyen. Vous avez besoin d'un constructeur par défaut pour les membres du tableau et il sera appelé, ensuite, vous pouvez faire l'initialisation que vous voulez dans le constructeur.

+8

Malheureusement, vous avez raison. +1 Notez que la syntaxe d'initialisation unifiée C++ 1x 'vous permettra de le faire. – sbi

+10

@sbi: Je l'appelle encore "C++ 0x". Bien sûr, je donne aussi parfois mon âge à 38 ans et j'espère pouvoir prendre ma retraite à l'âge de 3 ans. –

+2

@David: Je l'appelle C++ 1x (en espérant qu'il sera publié dans les 9,5 prochaines années). – sbi

1

Seul le constructeur par défaut peut être appelé lors de la création d'objets dans un tableau.

16

À l'heure actuelle, vous ne pouvez pas utiliser la liste d'initialisation pour les membres du groupe. Vous êtes coincé le faire à la dure.

class Baz { 
    Foo foo[3]; 

    Baz() { 
     foo[0] = Foo(4); 
     foo[1] = Foo(5); 
     foo[2] = Foo(6); 
    } 
}; 

En C++ 0x vous pouvez écrire:

class Baz { 
    Foo foo[3]; 

    Baz() : foo({4, 5, 6}) {} 
}; 
+0

Un constructeur à un argument sera appelé pour un int sauf si vous déclarez le constructeur explicite. – jmanning2k

+0

intéressant ... J'aurais probablement dû utiliser autre chose que 'int' dans mon exemple, car c'est trop" facile "à gérer. :-) –

1

Dans le cas particulier lorsque le tableau est un membre de données de la classe que vous ne pouvez pas l'initialiser dans la version actuelle de la langue. Il n'y a pas de syntaxe pour ça. Fournissez un constructeur par défaut pour les éléments de tableau ou utilisez std::vector.

Un groupe autonome peut être initialisé avec initialiseur global

Foo foo[3] = { 4, 5, 6 }; 

mais malheureusement il n'y a pas de syntaxe correspondante pour la liste d'initialisation du constructeur.

0

Aucune syntaxe de construction de tableau ne peut être utilisée dans ce contexte, du moins pas directement. Vous pouvez accomplir ce que vous essayez d'accomplir quelque chose par le long des lignes de:

Bar::Bar() 
{ 
    static const int inits [] = {4,5,6}; 
    static const size_t numInits = sizeof(inits)/sizeof(inits[0]); 
    std::copy(&inits[0],&inits[numInits],foo); // be careful that there are enough slots in foo 
} 

... mais vous aurez besoin de donner Foo un constructeur par défaut.

2

Cela semble fonctionner, mais je ne suis pas convaincu qu'il est juste:

#include <iostream> 

struct Foo { int x; Foo(int x): x(x) { } }; 

struct Baz { 
    Foo foo[3]; 

    static int bar[3]; 
    // Hmm... 
    Baz() : foo(bar) {} 
}; 

int Baz::bar[3] = {4, 5, 6}; 

int main() { 
    Baz z; 
    std::cout << z.foo[1].x << "\n"; 
} 

Sortie:

$ make arrayinit -B CXXFLAGS=-pedantic && ./arrayinit 
g++ -pedantic arrayinit.cpp -o arrayinit 
5 

caveat emptor.

Editer: non, Comeau le rejette.

Autre modification: C'est une sorte de tricherie, elle pousse simplement l'initialisation de tableau par membre à un endroit différent.Donc il faut encore Foo d'avoir un constructeur par défaut, mais si vous n'avez pas std::vector alors vous pouvez mettre en œuvre pour vous-même le strict minimum absolu dont vous avez besoin:

#include <iostream> 

struct Foo { 
    int x; 
    Foo(int x): x(x) { }; 
    Foo(){} 
}; 

// very stripped-down replacement for vector 
struct Three { 
    Foo data[3]; 
    Three(int d0, int d1, int d2) { 
     data[0] = d0; 
     data[1] = d1; 
     data[2] = d2; 
    } 
    Foo &operator[](int idx) { return data[idx]; } 
    const Foo &operator[](int idx) const { return data[idx]; } 
}; 

struct Baz { 
    Three foo; 

    static Three bar; 
    // construct foo using the copy ctor of Three with bar as parameter. 
    Baz() : foo(bar) {} 
    // or get rid of "bar" entirely and do this 
    Baz(bool) : foo(4,5,6) {} 
}; 

Three Baz::bar(4,5,6); 

int main() { 
    Baz z; 
    std::cout << z.foo[1].x << "\n"; 
} 

z.foo n'est pas en réalité un tableau, mais il semble à peu près autant qu'un comme un vecteur. Ajout de begin() et end() fonctions à trois est trivial.

+0

+ 1 pour essayer de faire une solution –

+0

... et cela me donne quelques idées qui pourraient fonctionner pour ma situation plus proprement que ce que j'ai. Merci! –

0

idées d'un esprit tordu:

class mytwistedclass{ 
static std::vector<int> initVector; 
mytwistedclass() 
{ 
    //initialise with initVector[0] and then delete it :-) 
} 

}; 

maintenant mis cette initVector à quelque chose u veulent avant u instancier un objet. Vos objets sont ensuite initialisés avec vos paramètres.

7

Malheureusement, il n'y a aucun moyen d'initialiser les membres du groupe jusqu'à C++ 0x.

Vous pouvez utiliser un std :: vector et repousser les instances Foo dans le corps du constructeur.

Vous pouvez donner à Foo un constructeur par défaut (peut être privé et faire de Baz un ami).

Vous pouvez utiliser un objet de tableau qui est copiable (boost ou std :: tr1) et initialiser à partir d'un tableau statique:

#include <boost/array.hpp> 

struct Baz { 

    boost::array<Foo, 3> foo; 
    static boost::array<Foo, 3> initFoo; 
    Baz() : foo(initFoo) 
    { 

    } 
}; 

boost::array<Foo, 3> Baz::initFoo = { 4, 5, 6 }; 
+0

+1. Je me demandais pourquoi personne n'avait pensé à ça, jusqu'à ce que j'aie vu ta réponse. 'array 'est trivial à implémenter, et ce n'est ni sauvage ni fou. Vous pourriez écrire une fonction comme 'array create() {array a = {...}; retourner a; } 'pour éviter la variable statique, aussi. –

+0

Cela me semble aussi évident, même si le support des templates est faible sur le compilateur cible (pas de 'std :: vector' semble fishy) une approche de génération fonctionnerait (préprocesseur ou script générant les classes nécessaires). –

3

Vous pouvez utiliser C++ 0xauto mot-clé avec spécialisation de modèle sur par exemple une fonction nommée boost::make_array() (similaire à make_pair()). Pour le cas où N est 1 ou 2 arguments on peut alors écrire variante A comme

namespace boost 
{ 
/*! Construct Array from @p a. */ 
template <typename T> 
boost::array<T,1> make_array(const T & a) 
{ 
    return boost::array<T,2> ({{ a }}); 
} 
/*! Construct Array from @p a, @p b. */ 
template <typename T> 
boost::array<T,2> make_array(const T & a, const T & b) 
{ 
    return boost::array<T,2> ({{ a, b }}); 
} 
} 

et variante B comme

namespace boost { 
/*! Construct Array from @p a. */ 
template <typename T> 
boost::array<T,1> make_array(const T & a) 
{ 
    boost::array<T,1> x; 
    x[0] = a; 
    return x; 
} 
/*! Construct Array from @p a, @p b. */ 
template <typename T> 
boost::array<T,2> make_array(const T & a, const T & b) 
{ 
    boost::array<T,2> x; 
    x[0] = a; 
    x[1] = b; 
    return x; 
} 
} 

GCC-4,6 avec -std=gnu++0x et -O3 génère le exactement le même code binaire pour

auto x = boost::make_array(1,2); 

utilisant à la fois A et B comme pour

boost::array<int, 2> x = {{1,2}}; 

Pour types définis par l'utilisateur (UDT), cependant, les résultats variante B dans un constructeur de copie supplémentaire, qui habituellement ralentir les choses, et devrait donc être évitée.

Notez que boost::make_array erreurs lors de l'appel avec un tableau explicite char littéraux comme dans le cas suivant

auto x = boost::make_array("a","b"); 

Je crois que c'est une bonne chose que const char* littéraux peut être trompeur dans leur utilisation.

modèles VARIADIC, disponibles dans GCC depuis 4.5, peut encore être utilisé de réduire tout le code-plaque de la chaudière spécialisation de modèle pour chaque N dans une définition de modèle unique de boost::make_array() défini comme

/*! Construct Array from @p a, @p b. */ 
template <typename T, typename ... R> 
boost::array<T,1+sizeof...(R)> make_array(T a, const R & ... b) 
{ 
    return boost::array<T,1+sizeof...(R)>({{ a, b... }}); 
} 

Cela fonctionne à peu près comme nous l'espérons. Le premier argument détermine boost::array l'argument modèle T et tous les autres arguments sont convertis en T. Dans certains cas, cela peut ne pas être souhaitable, mais je ne suis pas sûr de savoir si cela est possible de spécifier en utilisant des modèles variadiques.

Peut-être boost::make_array() devrait aller dans les bibliothèques Boost?

+0

merci mais C++ 0x n'est pas disponible sur les processeurs embarqués bas de gamme (les compilateurs C++ sont assez difficiles à trouver) –

-3
class C 
{ 
    static const int myARRAY[10]; // only declaration !!! 

    public: 
    C(){} 
    } 

const int C::myARRAY[10]={0,1,2,3,4,5,6,7,8,9}; // here is definition 

int main(void) 
{ 
    C myObj; 
    } 
25

Juste pour mettre à jour cette question pour 11 C++, cela est maintenant possible de faire à la fois et très naturelle:

struct Foo { Foo(int x) { /* ... */ } }; 

struct Baz { 
    Foo foo[3]; 

    Baz() : foo{{4}, {5}, {6}} { } 
}; 

Ces accolades peuvent également être élidés pour une encore plus concise:

struct Baz { 
    Foo foo[3]; 

    Baz() : foo{4, 5, 6} { } 
}; 

qui peut facilement être étendu à des tableaux multidimensionnels aussi:

struct Baz { 
    Foo foo[3][2]; 

    Baz() : foo{1, 2, 3, 4, 5, 6} { } 
}; 
+0

Y a-t-il une bonne façon d'initialiser Foo foo [3] [2] ;? – dshin

+2

@dshin De la même manière. Le plus braced: ': foo {{{1}, {2}}, {{3}, {4}}, {{5}, {6}}}', ou moins entretenu 'foo {{1, 2} , {3, 4}, {5, 6}} ', ou moins bien foo {1, 2, 3, 4, 5, 6}'. – Barry

+3

Existe-t-il une solution de contournement lorsque le constructeur 'Foo' est déclaré explicite? – dshin

-1

en studio visuel 2012 ou au-dessus, vous pouvez le faire comme ça

struct Foo { Foo(int x) { /* ... */ } }; 

struct Baz { 
    Foo foo[3]; 

    Baz() : foo() { } 
}; 
0

Vous pouvez le faire, mais ce n'est pas assez:

#include <iostream> 

class A { 
    int mvalue; 
public: 
    A(int value) : mvalue(value) {} 
    int value() { return mvalue; } 
}; 

class B { 
    // TODO: hack that respects alignment of A.. maybe C++14's alignof? 
    char _hack[sizeof(A[3])]; 
    A* marr; 
public: 
    B() : marr(reinterpret_cast<A*>(_hack)) { 
     new (&marr[0]) A(5); 
     new (&marr[1]) A(6); 
     new (&marr[2]) A(7); 
    } 

    A* arr() { return marr; } 
}; 

int main(int argc, char** argv) { 
    B b; 
    A* arr = b.arr(); 
    std::cout << arr[0].value() << " " << arr[1].value() << " " << arr[2].value() << "\n"; 
    return 0; 
} 

Si vous mettez dans votre code, j'espère que vous avoir une très bonne raison.

0

C'est ma solution pour votre référence:

struct Foo 
{ 
    Foo(){}//used to make compiler happy! 
    Foo(int x){/*...*/} 
}; 

struct Bar 
{ 
    Foo foo[3]; 

    Bar() 
    { 
     //initialize foo array here: 
     for(int i=0;i<3;++i) 
     { 
      foo[i]=Foo(4+i); 
     } 
    } 
};