Basisklasse initialisieren?



  • 🤡 Seid gegrüßt 🕶

    Ich habe eine template-Klasse mit einem N-großen Array. Dieses wird standardmäßig mit irgendwas gefüllt. Leite ich diese Klasse ab um einen spezialisierten Konstruktor für einen bestimmten Wert für N zu erhalten wird (laut meinem testprogramm) leider mein array durch konstruktor a überflüssigerweise gefüllt. (Darum soll sich ja jetzt die abgeleitete Klasse kümmern)

    template<const int N>
    struct a_t
    {
    	char name[N];
    
    	a_t(){ 
    		fill(name, name+N, 'a');
    		cout << "a" << endl; 
    	}
    };
    
    struct b_t : public a_t<3>
    {
    	b_t(char a, char b, char c) { 
    		name[0] = a; name[1]=b; name[2]=c;
    		cout << "b" << endl; 
    	}
    };
    
    int main()
    {	
    	b_t b;
    }
    

    a
    b

    Ich könnte ja nun sowas in der Art basteln:

    template<class N>
    class a_t
    {
    protected:
        class doNoting { /*Magische leere*/ };
        a_t(doNothing) {}
    
    public:
        char name[N];
    
        a_t();
    };
    
    b_t::b_t(char a, char b, char c) : a_t<3>(doNothing()) { /* ... */ }
    

    Aber irgendwie gefällt mir das nicht ^^ 🙄
    Wie würdet ihr sowas machen?



  • Pigeon schrieb:

    Ich habe eine template-Klasse mit einem N-großen Array. Dieses wird standardmäßig mit irgendwas gefüllt. Leite ich diese Klasse ab um einen spezialisierten Konstruktor für einen bestimmten Wert für N zu erhalten wird (laut meinem testprogramm) leider mein array durch konstruktor a überflüssigerweise gefüllt. (Darum soll sich ja jetzt die abgeleitete Klasse kümmern)

    ...

    Aber irgendwie gefällt mir das nicht ^^ 🙄
    Wie würdet ihr sowas machen?

    Ich würde so etwas möglichst garnicht machen. Meine Ansicht ist das es in den seltensten Fällen gut ist wenn abgeleitete Klassen Kenntnisse von der Implementierung der Basisklasse haben müssen. Aber was möglich wäre, ist einen protected Konstruktor zu verwenden der etwas anderes macht als der öffentliche:

    class A
    {
      protected:
        A(bool) // Irgendeine Dummy-Angabe zur Unterscheidung
        { /* ... */ }
      public:
        A()
        { /* ... */ }
    };
    
    class B : public A
    {
      public:
        B()
        : A(true)
        {}
    };
    


  • Hi,

    prinzipiell stößt Du hier auf einen Grund, warum Basisklassen möglichst abstrakt sein sollen (so raten zumindest die "Gurus" - wohl nicht ohne Grund).
    Außerdem ist das Verhalten vollkommen in Ordnung, denn Du sagst ja: "Jedes A wird erstmal mit 'a's initialisiert" ... und das trifft natürlich auch auf A's zu, die sogar noch B's sind ("Schnittstellenzusage").
    Dein einziges Problem besteht in Deinem ästhetischen Empfinden (u.g. Perfomanceaspekten, die Du aber überhaupt nicht belegst).

    Als "Ausweg" fände ich folgende Abwandlung Deiner Idee gar nicht so abwegig:

    template<class N>
    class a_t
    {
    public:
        char name[N];
        a_t() {}
        a_t(char c) { fill(name, name+N, 'a'); }
    };
    

    Da ist die Schnittstelle klar: Wer einen Initialisierer angibt, bekommt name initialisiert ... wer nicht, der nicht - egal, ob "Abkömmlinge" oder nicht (es kann ja auch noch andere Nutzer geben, die a_t lieber selbst initialisieren möchten).

    Gruß,

    Simon2.



  • asc schrieb:

    ...Aber was möglich wäre, ist ...

    .. genau Pigeons eigener Vorschlag. 😉

    Gruß,

    Simon2.



  • Simon2 schrieb:

    asc schrieb:

    ...Aber was möglich wäre, ist ...

    .. genau Pigeons eigener Vorschlag. 😉

    Das Passiert wenn man wegen grade einkommenden Emails nicht fertigliest 😉



  • Simon2 schrieb:

    Dein einziges Problem besteht in Deinem ästhetischen Empfinden (u.g. Perfomanceaspekten, die Du aber überhaupt nicht belegst).

    Das kann ich selbstverständlich so nicht auf mir sitzen lassen! 😃
    Belegt folgendes den Performance-Unterschied? (Wie zu erwarten dauerts doppelt so lange das array 2x zu beschreiben) Das ist jedoch mein erster Performance-test-versuch, also .... 🙄

    #include <iostream>
    #include <ctime>
    using namespace std;
    
    template<const int N>
    class a_t
    {
    protected:
    	class UNITIALIZED {};
    
    	a_t(UNITIALIZED) {}
    
    public:
    	char name[N];
    
    	a_t(){ 
    		fill(name, name+N, 'a');
    	}
    };
    
    struct b_t : public a_t<300>
    {
    	b_t() : a_t<300>() { 
    		fill(name, name+300, 'b');
    	}
    };
    
    struct c_t : public a_t<300>
    {
    	c_t() : a_t<300>(UNITIALIZED()) { 
    		fill(name, name+300, 'c');
    	}
    };
    
    int main()
    {	
    	clock_t TEST1_start, TEST1_end, TEST2_start, TEST2_end;
    
    	TEST1_start = clock();
    	for(unsigned i=0; i<2000000; ++i)
    		b_t b;
    
    	TEST1_end = clock();	
    
    	TEST2_start = clock();	
    	for(unsigned i=0; i<2000000; ++i)
    		c_t c;
    	TEST2_end = clock();	
    
    	cout << "Test 1: " << TEST1_end - TEST1_start << endl;
    	cout << "Test 2: " << TEST2_end - TEST2_start << endl;
    }
    

    asc schrieb:

    Ich würde so etwas möglichst garnicht machen. Meine Ansicht ist das es in den seltensten Fällen gut ist wenn abgeleitete Klassen Kenntnisse von der Implementierung der Basisklasse haben müssen.

    Sicherlich hast du Recht! Meine Abgeleiteten Klassen sollen eigendlich vorwiegend nur dazu dienen, einen bequemeren Konstruktor und andere lebenserleichterne Funktionen einzufügen für die wohl am meisten gebrauchten Werte für N (const int N der template-Klasse):

    template<const int N>
    struct vcon
    {
        vcon() {}
        vcon(char name[N& data]) { copy.... };
        char name[N];
    };
    
    struct vcon3 : public vcon<3>
    {
        vcon3(char ID1, char ID2, char ID3);
    };
    
    //Ist das praktisch? :
    vcon<3> v;
    v.name[0] = 'a';
    v.name[1] = 'b';
    v.name[2] = 'c';
    
    //Oder:
    char name[] = {'a', 'b', 'c'}; //Lokale variable die mehr oder weniger lang unnötig speicher raubt 
    vcon<3> v(name);
    
    //Oder doch das 
    vcon3 v('a', 'b', 'c');
    

    Ich denke, ich werds nun so machen:

    template<const int N>
    struct a_t
    {
        a_t() {}
        a_t(char INIT) { fill(name, name+N, INIT); }
    };
    
    struct a3_t : public a_t<3>
    {
        a3_t(char a, char b, char c) : a_t<3>() { name[0] = a .... }
    };
    

    Was haltet ihr davon? 🙂



  • Pigeon schrieb:

    Was haltet ihr davon? 🙂

    Wobei ich mich Frage, ob du dir das Leben nicht wie folgt einfacher machen kannst:

    #include<tr1/array>
    
    int main()
    {
      std::tr1::array<char, 3> a = {{ 'a','b','c' }};
      // Zugriff z.B. über a[2]
    }
    

    Zumindest ähneln sich IMHO dein Konstrukt und diese Klasse schon ein wenig...

    cu André



  • Ja gut dass dus Ansprichst! Mir ist ein Rätzel wofür das gut ist!? Was bitte bringt diese Klasse? Ich kann sie ja leider auch nichtmal verwenden, da - sofern ich sie ableite - das array nicht mehr initialisieren kann ... 😕 😕



  • Pigeon schrieb:

    Ja gut dass dus Ansprichst! Mir ist ein Rätzel wofür das gut ist!? Was bitte bringt diese Klasse? Ich kann sie ja leider auch nichtmal verwenden, da - sofern ich sie ableite - das array nicht mehr initialisieren kann ... 😕 😕

    Da magst du auf der einen Seite Recht haben (Die STL-Klassen sind auch nicht wirklich fürs ableiten gedacht), auf der anderen Seite verstehe ich deinen Aufwand auch ehrlich gesagt nicht, da du auch nichts anderes machst als ein Setzen (Eine Initialisierung ist das nicht).

    Dies kann man auch mit den von mir geschriebenen Einzeiler machen ohne gleich eine weitere Klasse zu erzeugen. Von der restlichen Effizienz her würde ich persönlich auch immer erst auf die STL-Container tippen...

    cu André



  • Pigeon schrieb:

    ...
    Belegt folgendes den Performance-Unterschied? ...

    Äh - ich habe nicht abgestritten, dass es einen Performance-Unterschied gibt, sondern einen Beleg für ein Performance-Problem !
    Natürlich dauert es länger, wenn der Rechner mehr tun muss - die Frage ist nur: Wie relevant ist das ?
    Auto-Vergleich: Natürlich ist ein F1-Ferrari unbestreitbar schneller als ein Fiat Punto - aber rechtfertigen die "Nachteile" (Kosten, mangelhafte Verkehrssicherheit, schlechte Parkeigenschaften, ....) die Anschaffung eines F1-Ferraris, um damit Zigarettenholen zu fahren ?

    Für die Aufgabe "Zigarettenholen", ist der Punto eindeutig das geeignetere Werkzeug - obwohl er objektiv gesehen "weniger performt".

    Auf Dein Beispiel bezogen: Eine konkrete Basisklasse birgt an sich schon einige Probleme ... aber wenn sie sich, je nachdem ob/von wem sie abgeleitet wurde, unterschiedlich verhält, ist das ein Nachteil, der sich erst einmal rechnen muss (durch den Wegfall grooooßer Probleme - in der Praxis).
    Wenn das tatsächlich ein Problem darstellen könnte, würde ich trotzdem lieber versuchen, das Design passend zu ändern (z.B. dass die Basisklasse keine Initialisierung garantiert).

    Gruß,

    Simon2.


Anmelden zum Antworten