2009-04-22 6 views
0

considèrent l'algorithme suivant avec les tableaux:Fonction pour manipuler conteneur d'objets de base/dérivés

class MyType; 
{ 
    // some stuff 
} 

class MySubType:MyType 
{ 
    // some stuff 
} 

void foo(MyType** arr, int len) 
{ 
    for (int i = 0;i<len;i++) 
     // do something on arr[i]-> 
} 

void bar() 
{ 
    MySubType* arr[10]; 
    // initialize all MySubType*'s in arr 
    foo(&arr, 10); 
} 

Rien d'extraordinaire ici. Ma question est - comment puis-je faire cela avec des modèles?

void foo(std::vector<MyType>& s) 
{ 
    std::vector<MyType>::iterator i; 
    for (i = s.begin(); i != s.end(); i++) 
     // do stuff on *i 
} 

donc, dans le bar, je ne peux pas le faire:

void bar() 
{ 
    std::vector<MySubType> s; 
    foo(s); // compiler error 
} 

erreur: initialisation invalide de référence de type std::vector<MyType, std::allocator<MyType> >& d'expression de type std::vector<MySubType, std::allocator<MySubType> >

Est-il possible de le faire quelque chose comme ça?

En fait, s'il y a une façon de le faire:

std::vector<MySubType> s; 
std::vector<MyType>& t = s; 

Je serais heureux ...

Répondre

8

Cela pourrait résoudre votre problème

template <typename T> 
void foo(std::vector<T>& s) 
{ 
    typename std::vector<T>::iterator i; 
    for (i = s.begin(); i != s.end(); i++) 
     // do stuff on *i 
} 
+0

Soit cela ou des pointeurs je pense. –

+0

J'aime ça, mais j'ai dû le changer un peu. Je mis typename devant T dans le modèle et j'ai aussi dû mettre typename avant std :: vector ... ne sais pas pourquoi ... pas très bon avec des modèles – paquetp

+0

La raison pour laquelle vous deviez mettre "template " est qu'il existe deux types de paramètres de gabarit: les types et les entiers, et vous devez dire quel genre est T. Vous avez dû mettre "typename std :: vector :: iterator i" parce que le compilateur ne peut pas dire que ".. . :: itérateur "est un type sans aide - sans" typename "il pense que" itérateur "est un membre de données du vecteur . –

3

est ici le problème avec cela - si le point de s et t au même objet, ce qui est pour vous arrêter de mettre MyOtherSubType (pas lié à MySubType) dans t? Cela ferait s contenir des objets qui ne sont pas MySubType. Je ne connais aucun langage de programmation de type sécurisé qui vous permet de faire cela. Si elle était autorisée, imaginez les problèmes que nous aurions:

//MySubType inherits from MyType 
//MyOtherSubType also inherits from MyType 

std::vector<MySubType> s; 
std::vector<MyType>& t = s; 

MyOtherSubType o; 
t.push_back(o); 

Puisque t et s sont exactement le même objet sous différents noms, l'élément à s [0] est quelque chose qui ne MySubType. Grand problème - nous ne pourrions jamais être sûrs que nos vecteurs contenaient les types qu'ils sont supposés! Ainsi, le compilé l'interdit.

+0

Il y a un certain nombre qui supporte ce type de covariance, mais pas C++ –

+0

Mais je n'en connais pas ;-) Bien sûr, les langages faiblement typés comme python vous le permettent, mais y a-t-il des types sécurisés langue qui vous permet? – Smashery

+0

Java le fait, mais vous devez spécifier si vous faites de la covariance ou de la contravariance. Effective Java introduit le concept "PECS": producteur étend, consommateur super. Qu'est-ce que cela signifie est-ce (si vous traitez avec le type T): si un conteneur entrant est utilisé pour obtenir des éléments de (conteneur utilisé en tant que producteur), vous spécifiez cela comme "Collection "; s'il est utilisé pour placer des objets dans (conteneur utilisé comme consommateur), vous spécifiez ceci comme "Collection ". –

0

C++ ne prend pas en charge la covariance sur les types de gabarit, pas plus que ne le fait actuellement C#, pour les mêmes raisons. Si vous voulez faire cela, il est préférable de faire en sorte que votre vecteur soit modélisé sur un type d'interface commun (des pointeurs de ce type), et que la fonction foo accepte un vecteur de ce type.

+0

Ce genre de chose est fait tout le temps en C++. – Arafangion

+0

Tout le temps? Vraiment? Vous pouvez assigner entre les classes modèles où les paramètres de modèle ne sont pas les mêmes? –

+0

Doit être vecteur de pointeurs de classe de base à travailler. Et puisque STL et les pointeurs bruts sont mal conseillés, des pointeurs intelligents. – paxos1977

0

Malheureusement, il n'y en a pas. Différentes spécialisations de modèles sont considérées comme des classes différentes; vous ne pouvez pas convertir entre std::vector<MyType> et std::vector<MySubType>.

Vous pouvez diviser le bit commun "do stuff" de foo() en une fonction distincte et avoir une boucle distincte sur chaque vecteur (ou éventuellement utiliser std::foreach).

2

Puisque vous dites: « Rien d'extraordinaire ici », je ne suis pas sûr que vous réalisez cela:

Votre code d'origine est cassé; ou si vous avez de la chance, il n'est peut-être pas cassé en ce moment, mais il est assez fragile et cassera dès que quelqu'un fera quelque chose de plus que de regarder la classe MySubType.

Le problème est que vous passez un MyType* à foo() mais il pointe vraiment vers un tableau de MySubType. Si un objet MySubType est plus grand qu'un objet MyType (ce qui est assez probable si vous avez ajouté quelque chose à la classe dérivée), l'arithmétique de pointeur effectuée dans la fonction foo() sera incorrecte.

Ceci est l'un des pièges sérieux et classiques des tableaux d'objets de classe dérivés.

+0

oh, ouais, votre droite, désolé - je voulais faire cela avec un tableau de pointeurs – paquetp

+0

+1. L'OP a corrigé le problème avec le code basé sur le pointeur d'origine, mais il reste avec le code vectoriel - dans un premier temps, vous devez déclarer les vecteurs comme vecteur et vector respectivement. –

0

Utiliser coup de pouce (pour pointeur intelligent):

foo(std::vector<boost::shared_ptr<MyType> >& v) 
{ 
    std::for_each(v.begin(), 
        v.end(), 
        do_something); 
} 

bar() 
{ 
    std::vector<boost::shared_ptr<MyType> > s; 
    // s.push_back(boost::shared_ptr<MyType> (new MySubType())); 
    foo(s); 
} 
+0

Je ne vois pas comment boost résout le problème original ici. – Tom

+0

Boost pour les pointeurs intelligents, le pointeur vers la classe de base résout le problème. – paxos1977

6

développiez kuoson's answer, le idiomatiques de style C++ est de passer itérateurs à une fonction plutôt que des conteneurs.

template<typename Iterator> 
void foo(const Iterator & begin, const Iterator & end) 
{ 
    Iterator i; 
    for (i = begin; i != end; ++i) 
     // do stuff on *i 
} 
+0

désolé Mark, j'ai mieux aimé la réponse de Kuoson ... – paquetp

+0

Rien à redire, merci d'avoir laissé le commentaire. –

0

Si je sais ce qui est le code réel derrière ces foobars alors peut-être je sais mieux, mais n'y est pas déjà une solution pour votre problème dans la STL?

for_each(s.begin(), s.end(), DoStuffOnI()); 

Il suffit de mettre votre « faire des choses sur * i » code dans une fonction ou un foncteur:

struct DoStuffOnI : public std::unary_function<MyType&,void> { 
    void operator()(MyType& obj) { 
     // do stuff on *i 
    } 
}; 

Si vous êtes pris la peine d'envoyer deux paramètres de distance au lieu d'un, alors ok, peut-être vous pouvez faire quelque chose comme:

template<typename In> 
struct input_sequence_range : public std::pair<In,In> { 
    input_sequence_range(In first, In last) : std::pair<In,In>(first, last) 
    { 
    } 
}; 

template<typename C> 
input_sequence_range<typename C::iterator> iseq(C& c) 
{ 
    return input_sequence_range<typename C::iterator>(c.begin(), c.end()); 
} 

template<typename In, typename Pred> 
void for_each(input_sequence_range<In> r, Pred p) { 
    std::for_each(r.first, r.second, p); 
} 

appellent alors for_each comme ceci:

for_each(iseq(s), DoStuffOnI()); 
+0

J'aime ça, mais je pense que la solution de template de kuoson est plus élégante. Merci wilhelmtell. – paquetp

1

Si vous voulez stocker des objets de types différents MyType -dérivé dans un seul vecteur (comme je le suppose, bien que cela ne soit pas nécessaire pour cet exemple particulier), vous devrez utiliser un std::vector<MyType*> au lieu de std::vector<MyType>. Cette suggestion est analogue à celle proposée par Michael Burr pour votre code de pointeur original.

Cela a le effet secondaire malheureux que vous ne pouvez pas convertir implicitement un std::vector<MySubType*> à un std::vector<MyType*> appeler foo() avec. Mais le code de conversion n'est pas trop onéreux:

void foo(std::vector<MyType*>& s) 
{ 
    ... 
} 

void bar() 
{ 
    std::vector<MySubType*> s; 

    // Populate s 
    ... 

    std::vector<MyType*> u(s.begin(), s.end()); // Convert 
    foo(u); 
} 

Ou, juste avoir bar() utilisent un std::vector<MyType*> depuis le début.

+0

merci j_random_hacker ... c'est une solution décente, mais je préfère mieux la fonction template ... J'ai aussi omis de mentionner que je ne le fais pas sur un std :: vector, mais sur quelque chose comme un std :: vecteur. Les éléments du conteneur spécial ne peuvent pas être des pointeurs par conception (ils doivent tous au moins être d'un type spécifique, ce qui n'est pas un pointeur). Je n'ai pas expliqué cela dans mon post original parce que c'est trop tapant pour expliquer. :( – paquetp

+0

Aucun problème :) Si les éléments sont stockés en tant qu'objets plutôt que de pointeurs, alors chaque fonction * que vous voulez utiliser sur une variété de types (par exemple MyType et MySubType) devra être transformée en fonction modèle. –