Willkommen ~Gast!
Registrieren || Einloggen || Hilfe/FAQ || Staff
Probleme mit der Registrierung im Forum? Melde dich unter registerEin Bild.
Autor Beitrag
000
12.08.2010, 14:33
ElChupkapres



Nächstes Problem das ich habe, hoffe auch hierbei kann mir geholfen werden ;)

Ich habe ein Template welches ganz grob folgendermaßen aussieht:

Quellcode:template <class T1, class T2>
class A
{

private:    
    
    T1 *m_t1;
    T2 *m_t2;

public:

    // Standard constructor
    A() {

        m_t1 = new T1;
        m_t2 = new T2;
    
    };
};
Mein Problem ist jetzt, das ich einige Spezialfälle habe. Angenommen U ist ein beliebiger Typ und X / Y sind zwei Spezielle Typen. Die speziellen Typen können nur zusammen verwendet werden. Bei den Typen handelt es sich nicht um primitive Datentypen sondern um Klassen.

Es geht also folgendes:

T1 = U;
T2 = U;

T1 = X;
T2 = Y;

Bei folgenden Kombinationen gibt es kein sinnvolles Ergebnis bei der Berechnung die mit den Typen durchgeführt werden.

T1 = X;
T2 = U;

T1 = U;
T2 = Y;

Daher soll es nicht möglich sein, diese Kombination an Template-Parametern zu übergeben (Compiler-Fehler z.B.) oder was auch denkbar wäre, das einer der Parameter geändert wird so das die Kombination T1 = X, T2 = Y erzwungen wird.

Jetzt gibt es ja die Möglichkeit der Template-Spezialiserung, dann müsste ich aber so weit ich weiß sämtlichen Code aus dem Standard-Template 1:1 kopieren (bis auf den Konstruktor), was ansich ja nicht schön ist.

Ich hoffe ich habe mein Problem verständlich beschrieben und ihr könnt mir ein paar Tips geben wie ich es einigermaßen elegant lösen kann.

--

zum Seitenanfang zum Seitenende Profil || Suche
001
12.08.2010, 16:48
Bluthund



Zitat:
Jetzt gibt es ja die Möglichkeit der Template-Spezialiserung, dann müsste ich aber so weit ich weiß sämtlichen Code aus dem Standard-Template 1:1 kopieren (bis auf den Konstruktor), was ansich ja nicht schön ist.
Richtig. Das ist nicht schoen. Aber wenn du bei einer vollstaendigen Spezialisierung eines Klassen-Templates viel Code 1:1 aus dem Standard-Template kopierst, war die Entscheidung das Template zu spezialisieren wahrscheinlich schon von vornherein eine bloede Idee. Du muesstest auch den Kontruktor neu schreiben, ausser du benoetigst in deinem spezialisierten Template keine Initialisierung ausserhalb des automatisch erzeugten Standard-Kontruktors. Oder was bringt dich zu der Annahme es nicht tun zu muessen?
Die Spezialisierung deines Templates (bspw. fuer <X,Y>) verhindert auch nicht, dass andere Instanziierungen des Templates vorgenommen werden koennen (z.B. A<X,U>).

Eine Moeglichkeit, die du haettest um nur vorgesehene Kombinationen zu forcieren, waere eine traits-Struktur zu erstellen, die deinem Code ermoeglicht kompatible Typen selbst zu finden.
Quellcode:#include <iostream>

using namespace std; // Beispielcode -- Importiere fuer Kuerze

class X {};

class Y {};

template<typename T>
struct A_compat_traits {
    // jeder Typ ist prinzipiell mit sich selbst nutzbar
    // erfuellt T1 = U; T2 = U
    typedef T CompatibleType;
};

template<>
struct A_compat_traits<X> {
    // Typ X nur mit Typ Y verwendbar
    // erfuellt T1 = X; T2 = Y
    typedef Y CompatibleType;

    // Das Spielchen hier ist natuerlich nicht-kommutativ
    // wenn auch T1 = Y; T2 = X gueltig ist, muesste analog ein
    // A_compat_traits-Template fuer Y spezialisiert werden
};

/* Damit liessen sich auch Typen komplett sperren indem du bspw.
*     typedef NoExistingCompatibleType CompatibleType;
* setzt, wobei NoExistingCompatibleType nicht konstruierbar ist (private ctor).
*/
class NoExistingCompatibleType {
    NoExistingCompatibleType();
};

class Z {};

template<>
struct A_compat_traits<Z> {
    // Typ Z kann nicht genutzt werden
    typedef NoExistingCompatibleType CompatibleType;
};

template<typename T>
class A {
    typedef T FirstType;
    typedef typename A_compat_traits<T>::CompatibleType SecondType;

    FirstType *m_t1;
    SecondType *m_t2;

public:
    // http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6
    A() : m_t1(new FirstType()), m_t2(new SecondType()) {}
    ~A() { delete m_t2; delete m_t1; }

    int sum() { return *m_t1 + *m_t2; } // Berechnung, die nur mit bestimmten Typen Sinn macht
};

int operator+(const X&, const Y&) {
    return 23; // sehr spezielle "Berechnung"
}

int operator+(const Y& lhs, const X& rhs) { // Kommutatives Plus
    return rhs + lhs;
}

int main() {
    cout << A<X>().sum() << endl; // 23
    cout << A<int>().sum() << endl; // 0

//  cout << A<Y>().sum() << endl; // Compiler-Fehler -- Berechnung nicht moeglich durch fehlenden operator+(Y&,Y&), weil hier das Standard-Template instanziiert wird
//  cout << A<Z>().sum() << endl; // Compiler-Fehler -- Z fuer Verwendung mit A gesperrt, durch nicht konstruierbaren Typ
}
edit: Beispiel fuer Sperrung eines Typs Z hinzugefuegt und Klassen X & Y eingedampft.
edit2: Memory-Leak in A entfernt.

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 12.08.2010 um 19:06 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
002
12.08.2010, 18:29
Bluthund



"Antwort erstellen" ist nicht "Editieren" ._.
Naja jetzt ist der Post einmal da, da kann ich auch noch ein bisschen Info addieren.

Eine aehnliche Loesung fuer Template-Constraints wird vom Chef selbst so beschrieben:
http://www2.research.att.com/~bs/bs_faq2.html#constraints

Ausserdem bietet Boost eine Bibliothek, um zur Compile-Zeit zu pruefen ob ein Template-Parameter ein bestimmtes Konzept erfuellt:
http://www.boost.org/doc/libs/1_36_0/libs/concept_check/concept_check.htm
Aber letzteres ist vielleicht gleich ein bisschen maechtig wenn man grad erst am Lernen ist.

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 12.08.2010 um 18:46 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
003
19.08.2010, 15:46
ElChupkapres



Vielen Dank für die Antwort, ist genau das was ich gesucht habe. Klappt Super!

Ich habs jetzt so gemacht:

Quellcode:class U {};    // verschiedene beliebige Typen1

class V {};    // verschiedene beliebige Typen2

class X {};    // spezieller Typ1

class Y {};    // spezieller Typ2

Quellcode:class NoExistingCompatibleType {
    NoExistingCompatibleType();
};

// Beliebiger Typ U kann mit beliebigen Typ V genutzt werden
template<typename T1, typename T2>
struct A_compat_traits {
        typedef T2 CompatibleType;
};

// X kann mit Y benutzt werden
template<>
struct A_compat_traits<X, Y> {
    typedef Y CompatibleType;
};

// X kann nicht mit beliebigen Typen V genutzt werden
template<typename T2>
struct A_compat_traits<X, T2> {
    typedef NoExistingCompatibleType CompatibleType;
};

// beliebiger Typ U kann nicht mit Y genutzt werden
template<typename T1>
struct A_compat_traits<T1, Y> {
    typedef NoExistingCompatibleType CompatibleType;
};

template <class T1, class T2>
class A
{

private:    
    
    typedef T1 FirstType;
    typedef typename A_compat_traits<T1, T2>::CompatibleType SecondType;

    FirstType *m_t1;
    SecondType *m_t2;

public:

    // Standard constructor
    A() : m_t1(new T1), m_t2(new T2) {
    };

    ~A() {

        delete m_t1;
        delete m_t2;
    };
};

Noch eine Frage:
A() : m_t1(new T1), m_t2(new T2) <-- mit einem Array das übergeben wird ist das nicht möglich oder?

--

zum Seitenanfang zum Seitenende Profil || Suche
004
19.08.2010, 17:10
Bluthund



T1 und T2 sind wenn ueberhaupt Arraytypen und keine Arrays.
Es wuerde funktionieren wenn du keine Pointer fuer die Membervariablen in A verwenden wuerdest (aber nur damit wir uns richtig verstehen: Initialisierungslisten funktionieren mit jedem Typ). Pointer auf Arrays (auch wenn die wahrscheinlich "niemand" verwendet) haben zuerst einmal eine "etwas" eigenwillige Deklarations-Syntax (dazu gleich mehr). Ein Pointer auf ein Array ist das, was du bekommst wenn du den Referenz-Operator (&) auf ein Array anwendest.
Von C herruehrend werden (C-)Arrays sehr oft aber nur ueber einen Pointer auf das erste Element uebergeben, da man Operationen, die man auf ein Array ausfuehren wuerde, in C auch auf einem Pointer ausfuehren kann. Ein Pointer darauf ist aber ein Pointer auf einen Pointer und kein Pointer auf ein Array.
Und so gibt dir auch new[] in C++ nur einen Pointer auf das erste Element eines angelegten C-Arrays.

Zur Syntax:
Ein Zeiger "a" auf ein Array mit 10 Elementen vom Typ int wuerde bspw. so aussehen:
Quellcode:int (*a)[10]; Das Problem ist: Wenn du new mittels
Quellcode:new int[10]; anweist dir Speicher fuer ein int-Array mit zehn Elementen zu holen bekommst du einen Quellcode:int* zurueck (von operator new[] selbst kommt void* zurueck aber die Konvertierung erfolgt hier automatisch und ist im Standard festgelegt). D.h. du bekommst einen Zeiger auf das erste Element, des Arrays. Das Template da oben wuerde aber deinen Zeigertyp mit der ersteren genannten Syntax deklarieren (was ja auch voellig korrekt ist, denn wenn T gleich int[10] ist dann ist fuer einen Zeiger darauf eben o.g. Syntax zu verwenden) und diese beiden Typen sind aber logischerweise nicht kompatibel.
Das Ganze wird zusaetzlich aber sowieso problematisch beim Loeschen, da Arraytypen mit new[] geholt werden und deshalb auch zwingend mit delete[] abgebaut werden muessen.

Du solltest dementsprechend auch schauen ob es wirklich zwingend rohe C-Pointer und Objekte auf dem Heap sein muessen oder ob es nicht vielleicht ein einfaches Objekt als Member auch tut (was schlussendlich auch auf dem Heap landet, wenn das enthaltende Objekt da liegt).
Gleichzeitig solltest du ausserdem beachten, dass fuer jede Typenkombination von T1 und T2 das Template fuer A instantiiert wird und bereits die Aenderung der Arraydimension einen neuen Typ hervorbringt. D.h. wenn du (nach einem Umstieg auf nicht-Pointer Member) im Code A<U,V>, A<U,V[1]> und A<U,V[2]> benutzen wuerdest, haettest du am Ende drei Klassen A fuer die jeweiligen Typkombinationen. Du solltest also genau schauen was als Templateparameter sinnvoll ist und was nicht vielleicht lieber ein Konstruktorparameter, der zur Laufzeit behandelt wird, sein sollte.

Loesche den von dir geholten Speicher auch besser genau in umgekehrter Reihenfolge (so wie es auch bei lokalen Instanzen beim Verlassen ihres Scopes geschieht). Tu es also so als wuerdest du einen Stack leeren (LIFO - Last In First Out, das letzte, was reingetan wurde, ist das erste, was wieder rausgeholt wird). Damit gehst du dem aus dem Weg, dass vielleicht eine spaeter aufgebaute Objektinstanz noch einen Verweis (in Form eines Pointers oder einer Referenz) auf ein vorher aufgebautes Objekt hatte (was ohnehin mit Vorsicht zu geniessen ist, fuer sowas gibt es bspw. Smart-Pointer) und diesen vielleicht in irgendeiner Art und Weise im Destruktor benoetigt. Wenn du genau in der selben Reihenfolge abbaust wie du aufgebaut hast, ist so ein Objekt aber bereits geloescht und dann fliegt dir der Destruktor des abhaengigen Objekts um die Ohren.
Beispiel:
Quellcode:class A {
    // (...) Wahnsinnig viel Zeug
public:
    // (...) ctor, dtor, etc.
    bool information() { /* stellt wahnsinnig wichtige Information in Form eines bool zur Verfuegung */ }
};
class B {
    // (...) viele Membervariablen
    A* my_A;
public:
    B(A* a) : my_A(a) { /* initialisiere mit Hilfe von my_A und dessen information()-Memberfunktion viele Membervariablen */ }
    ~B();
};

B::~B() {
    if (my_A->information()) {
         // tue etwas, das von der wahnsinnig wichtigen Information von my_A abhaengt
    }

    // und der Rest
}
Du kannst dir also in etwa ausrechnen was passiert wenn du jetzt
Quellcode:A* foo = new A;
B* bar = new B(foo);
delete foo;
delete bar;
lostrittst.

Du solltest ausserdem beachten, dass wenn du nur "new Type" verwendest bei POD-Typen (Plain Old Data) keine Wert-Initialisierung stattfindet (deswegen hatte ich oben im Beispiel "new Type()" verwendet). Die relevanten Stellen aus dem C++-Standard hat dieser Mensch hier zitiert: http://stackoverflow.com/questions/2468367/is-new-int10-valid-c/2468538#2468538

edit: Versucht den ersten Paragraphen etwas klarer zu gestalten.

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 19.08.2010 um 20:22 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche