2009-04-22 13 views
0

betrachten den folgenden Algorithmus mit Arrays:Funktion zur Manipulation Behälter von Basis-/Abgeleitete Objekte

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

Nichts Besonderes hier. Meine Frage ist - wie mache ich das mit Vorlagen?

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

so, in bar, ich kann das nicht tun:

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

Fehler: ungültige Initialisierung einer Referenz des Typs std::vector<MyType, std::allocator<MyType> >& von Expression von Typ std::vector<MySubType, std::allocator<MySubType> >

Gibt es irgendeine Art und Weise zu tun etwas wie das?

Grundsätzlich, wenn es eine Möglichkeit, dies zu tun:

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

Ich würde mich freuen ...

Antwort

8

Dies könnte Ihr Problem

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

Entweder das oder Zeiger würde ich denken. –

+0

Ich mag das, aber musste es ein bisschen ändern. Ich legte Typname vor T in Vorlage und ich musste auch Typname vor Std :: Vector setzen ... weiß nicht warum ... nicht sehr gut mit Vorlagen – paquetp

+0

Der Grund, warum Sie "Vorlage " setzen mussten ist Es gibt zwei Arten von Template-Parametern: Typen und Integer, und Sie müssen sagen, welche Art T ist. Sie mussten "Typname std :: vector :: iterator i" setzen, weil der Compiler das nicht sagen kann. . :: iterator "ist ein Typ ohne Hilfe - ohne" typename "denkt er, dass" iterator "ein Datenelement des Vektors ist. –

3

Hier ist das Problem mit diesem - wenn s und t auf das gleiche Objekt, was ist um Sie zu stoppen setzen MyOtherSubType (nicht mit MySubType verbunden) in t? Das würde Objekte enthalten, die nicht MySubType sind. Ich kenne keine typsichere Programmiersprache, mit der man das machen kann. Wenn es erlaubt war, die Probleme vorstellen, wir haben würden:

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

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

MyOtherSubType o; 
t.push_back(o); 

Da t und s ist genau das gleiche Objekt unter verschiedenen Namen, ist das Element, in s [0] etwas, das nicht MySubType ist. Riesiges Problem - wir konnten nie sicher sein, dass unsere Vektoren die Typen enthielten, die sie haben sollten! So verbietet das kompilierte es.

+0

Es gibt eine Reihe, die diese Art von Kovarianz unterstützt, nur nicht C++ –

+0

Aber ich kenne keine ;-) Natürlich, schwach-typisierte Sprachen wie Python werden Sie lassen, aber gibt es keine Typ-Safe Sprache, die Sie lässt? – Smashery

+0

Java tut, aber Sie müssen angeben, ob Sie Kovarianz oder Kontravarianz durchführen. Effektives Java führt das "PECS" -Konzept ein: Producer extends, consumer super. Dies bedeutet Folgendes (wenn Sie mit Typ T arbeiten): Wenn ein eingehender Container zum Abrufen von Elementen verwendet wird (Container wird als Produzent verwendet), geben Sie Folgendes an: "Sammlung "; Wenn es zum Einfügen von Elementen verwendet wird (Container, der als Konsument verwendet wird), geben Sie dies als "Sammlung " an. –

0

C++ unterstützt nicht die Kovarianz auf Schablonentypen, mehr als C# derzeit tut, viel aus den gleichen Gründen. Wenn Sie dies tun möchten, ist es am besten, Ihren Vektor auf einen allgemeinen Schnittstellentyp (von Zeigern auf diesen Typ) zu templatisieren und die Funktion foo einen Vektor dieses Typs akzeptieren zu lassen.

+0

Diese Art der Sache ist die ganze Zeit in C++ erledigt. – Arafangion

+0

Die ganze Zeit? "Ja wirklich?" Sie können zwischen Template-Klassen zuweisen, bei denen die Template-Parameter nicht übereinstimmen? –

+0

Muss Vektor von Basisklassen-Zeigern sein, um zu arbeiten. Und da STL und rohe Zeiger schlecht beraten sind, intelligente Zeiger. – paxos1977

0

Leider gibt es nicht. Unterschiedliche Template-Spezialisierungen werden als unterschiedliche Klassen betrachtet; Sie können nicht zwischen std::vector<MyType> und std::vector<MySubType> konvertieren.

Sie könnten das allgemeine Bit "do stuff" von foo() in eine separate Funktion aufteilen und eine separate Schleife über jeden Vektor haben (oder möglicherweise std::foreach verwenden).

2

Da Sie sagen: "Nichts Besonderes hier" beheben, ich Sie dies zu realisieren bin nicht sicher:

Ihr Original-Code gebrochen wird; oder wenn du Glück hast, ist es vielleicht gerade nicht kaputt, aber es ist ziemlich zerbrechlich und wird zerbrechen, sobald jemand mehr tut als nur auf die MySubType Klasse zu schauen.

Das Problem ist, dass Sie eine MyType* zu foo() übergeben, aber es zeigt wirklich auf ein Array von MySubType. Wenn ein MySubType Objekt größer als ein MyType Objekt ist (was sehr wahrscheinlich ist, wenn Sie etwas zur abgeleiteten Klasse hinzugefügt haben), dann wird die Zeigerarithmetik in der foo() Funktion falsch sein.

Dies ist eine der schwerwiegenden und klassischen Fallstricke von Arrays abgeleiteter Klassenobjekte.

+0

oh, yeah, Ihr Recht, tut mir leid - ich wollte dies mit einer Reihe von Zeigern tun – paquetp

+0

+1. Das OP hat das Problem mit dem ursprünglichen zeigerbasierten Code korrigiert, bleibt aber beim vektorbasierten Code - als ersten Schritt müssen Sie die Vektoren als Vektor bzw. Vektor deklarieren. –

0

Mit Boost (für Smart-Pointer):

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

Ich sehe nicht, wie Boost das ursprüngliche Problem hier löst. – Tom

+0

Boost für intelligente Zeiger, Zeiger auf Basisklasse löst das Problem. – paxos1977

6

Um auf kuoson's answer zu erweitern, der idiomatische C++ Stil ist Iteratoren auf eine Funktion eher als Behälter zu übergeben.

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

Entschuldigung Mark, ich mochte kuosons Antwort besser ... – paquetp

+0

Nichts falsch daran, danke für den Kommentar. –

0

Wenn ich weiß, würde was hinter diesem foobars der eigentliche Code ist dann vielleicht würde ich es besser wissen, aber nicht bereits eine Lösung für Ihr Problem in der STL?

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

einfach Ihre setzen „tun Sachen auf * i“ Code in einer Funktion oder ein Funktor:

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

Wenn Sie über wegzuschicken zwei Parameter gestört statt einem, dann ok, vielleicht Sie können, wie etwas tun:

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

nennen Dann for_each wie so:

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

Ich mag das, aber ich denke, die Vorlagenlösung von kuoson ist eleganter. Danke Wilhelmelt. – paquetp

1

Wenn Sie Objekte mehrerer verschiedener MyType-abgeleiteter Typen in einem einzigen Vektor speichern möchten (wie ich vermute, obwohl dies in diesem Beispiel nicht erforderlich ist), müssen Sie std::vector<MyType*> anstelle von std::vector<MyType> verwenden. Dieser Vorschlag entspricht dem Vorschlag von Michael Burr für Ihren ursprünglichen Zeigercode.

Dies hat die unglückliche Nebeneffekt, dass Sie nicht implizit konvertieren std::vector<MySubType*> zu std::vector<MyType*>foo() mit aufrufen können. Aber der Conversion-Code ist nicht zu belastend:

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

Oder hat nur bar() Gebrauch ein std::vector<MyType*> von Anfang an.

+0

danke j_random_hacker ... das ist eine anständige Lösung, aber ich bevorzuge die Template-Funktion besser ... Ich habe auch versäumt zu erwähnen, dass ich das eigentlich nicht auf einem std :: vector mache, sondern auf etwas wie einem std :: Vektor. Die Elemente in dem speziellen Container können keine Zeiger sein (alle müssen mindestens einen bestimmten Typ aufweisen, der kein Zeiger ist). Ich habe das in meinem ursprünglichen Beitrag nicht erklärt, weil es zu viel Tipparbeit ist, um es zu erklären. :( – paquetp

+0

Kein Problem :) Wenn die Elemente als Objekte und nicht als Zeiger auf sie gespeichert werden, dann muss * jede * Funktion, die Sie mit einer Vielzahl von Typen (zB MyType und MySubType) arbeiten möchten, zu einer Funktion gemacht werden Vorlage. –