Statische Memberfunktionen überschreiben



  • Hallo allerseits.

    Ich weiß, dass das was ich vorhabe nicht geht, suche also eine elegante Alternative. Die Problematik ist Folgende. Ich habe eine Basis- und eine abgeleitete Klasse:

    #include <iostream>
    #include <vector>
    
    class Base
    {
    public:
      static Base zero_like(const Base& b)
      {
        std::cout << "Builder of Base used.\n";
        return Base();
      }  
    };
    
    class Derived1 : public Base
    {
    public:
      Derived1(const std::vector<int>& data) : data(data) {}
      
      static Base zero_like(const Derived1& b)
      {
        std::cout << "Builder of Derived1 used.\n";
        return Derived1(std::vector<int>(b.data.size(), 0));
      }
      
    private:
      std::vector<int> data;
    };
    

    Eventuell können mehrere abgeleitete Klassen existieren. Ich möchte mit der statischen Funktion zero_like eine Kopie eines Objektes anlegen können, wobei aber alle Parameter mit 0 initialisiert werden. Den Copy-Konstruktor brauche ich an anderer Stelle, der soll nicht implementiert werden. Dieser soll es mir erlauben echte Kopien (also auch die Werte aus Derived1::data sollen kopiert wertden) anzulegen.

    Im Hauptprogramm möchte ich jetzt gern schreiben:

    int main()
    {
      Derived1 a({1,2,3});
      Base b = Base::zero_like(a); // I want to call Derived1::zero_like(a) here
    }
    

    Natürlich wird hier die Funktion Base::zero_like aufgerufen. Ich hätte allerdings gern, dass Derived1::zero_like aufgerufen wird, ohne aber den Typ explizit anzugeben, ohne irgendwelche Casts und Abfragen nach Datentyp.

    Das klingt alles etwas nach virtual und override, aber leider geht das bei statischen Memberfunktionen nicht.

    In meiner Anwendung kann man sich vorstellen, dass ich einen std::vector mit vielen Objekten vom Typ Derived1, Derived 2, etc. habe und je nach Typ die entsprechend als statische Funktion implementierte zero_like-Funktion aufrufen möchte.

    Vermutlich gibt es dafür eine ganz einfache Lösung, ich komme aber nicht drauf.

    Danke und viele Grüße
    Max



  • Dir ist klar dass die funktion in Derived1 "slicing erzeugt"? denn der Return type ist kein (smart)Pointer/reference sonder der reine Typ der Basis klasse.

    Was soll denn mit der zero_like methode überhaupt erreicht werden?
    Eine möglichkeit wäre es als member methode zu machen wodurch der input parameter wegfällt.
    Aber das ändert nichts daran, dass wenn Derived1::zero_like aufgerufen wird, dass die zurückgelieferte Instanz kein Derived1 ist!

    Da wäre es wohl eventuell geschickter ein methoden template zu nutzen?

    In meiner Anwendung kann man sich vorstellen, dass ich einen std::vector mit vielen Objekten vom Typ Derived1, Derived 2, etc. habe

    Wie sieht denn der std::vector aus? Hoffentlich nicht std::vector<Base>.
    Was ich bisher sehe könnte man meinen als würdest du eher aus der Java/C# welt kommen. Wo alle klassen Reference types sind. Was es in C++ aber nicht ist.



  • Mal abgesehen davon, dass zero_like einen (unique-)Pointer zurück geben muss (slicing), kannst du doch einfach freie Funktionen überladen:

    Base base(const Base&) {return Base{};} // (1)
    Derived1 base(const Derived1&) {return Derived1{};} // (2)
    ...
    base(Base{}); // calls (1)
    base(Derived1{}); // calls (2)
    
    


  • @Jockelx sagte in Statische Memberfunktionen überschreiben:

    Mal abgesehen davon, dass zero_like einen (unique-)Pointer zurück geben muss (slicing), kannst du doch einfach freie Funktionen überladen:

    Base base(const Base&) {return Base{};} // (1)
    Derived1 base(const Derived1&) {return Derived1{};} // (2)
    ...
    base(Base{}); // calls (1)
    base(Derived1{}); // calls (2)
    
    

    Da in seinem beispiel die Derived1 version zugriff auf private member benötigt sollte man erwähnen dass man dann hierfür eine friend deklaration/definition(?) braucht



  • Hallo,

    ihr habt natürlich Recht, das Minimalbeispiel, was ich mir ausgedacht habe ist tatsächlich Unsinn. Ich habe es mal etwas umgeschrieben und im Hauptprogramm einen Vektor definiert, so wie ich ihn später brauche:

    #include <iostream>
    #include <vector>
    
    class Base
    {
    public:
      static Base* zero_like(const Base& b)
      {
        std::cout << "Builder of Base used.\n";
        return new Base();
      }  
    };
    
    class Derived1 : public Base
    {
    public:
      Derived1(const std::vector<int>& data) : data(data) {}
      
      static Base* zero_like(const Derived1& b)
      {
        std::cout << "Builder of Derived1 used.\n";
        return new Derived1(std::vector<int>(b.data.size(), 0));
      }
      
    private:
      std::vector<int> data;
    };
    
    int main()
    {
      std::vector<Base*> vec;
      vec.push_back(new Derived1({1,2,3}));
      
      Base* b = Base::zero_like(*vec[0]); // I want to call Derived1::zero_like(a) here
    }
    

    Hier sind vermutlich smart-Pointer besser, aber die muss ich noch lernen ^^.

    Die Lösung einfach freie Funktionen zu überladen klingt erstmal vernünftig. Aber irgendwie gehören die ja schon zur Klasse, darum hielt ich statische Memberfunktionen für irgendwie passender.

    Vielleicht um mein Problem etwas besser zu verstehen. Motiviert ist das ganze von Python Numpy, dort kann man sich Vektoren, Matrizen, etc. definieren und mit der zeros_like-Funktion Vektoren/Matrizen der gleichen Dimension aber mit 0 initialisiert erzeugen:

    import numpy as np
    
    A = np.array([[1,2,3],[4,5,6]])
    B = np.zeros_like(A)
    

    Soetwas ähnliches habe ich auch vor.

    Danke euch und viele Grüße



  • Geht das nicht viel einfacher mit einem Klassen-Template?



  • @Max3000
    Naja, dann mach die Funktion halt nicht statisch. Ich meine, wenn du sowieso ein Objekt der Klasse Base bzw. einer von Base abgeleiteten Klasse brauchst (um darüber den Typ zu ermitteln), was ist dann der Sinn einer statischen Funktion? Also z.B. einfach:

    class Base {
    public:
        // ...
        virtual Base* zero_like() const {
            return new Base();
        }
    };
    
    class Derived1 : public Base {
    public:
        // ...
        Base* zero_like() const override {
            return new Derived1(std::vector<int>(data.size(), 0));
        }
    };
    
    int main()
    {
        std::vector<Base*> vec;
        vec.push_back(new Derived1({1,2,3}));
      
        Base* b = vec[0]->zero_like();
    }
    

    Man könnte jetzt argumentieren dass man damit keinen Schönheitswettbewerb gewinnt. Aber es ist einfach und es funktioniert.



  • @Max3000 sagte in Statische Memberfunktionen überschreiben:

    Base* b = Base::zero_like(*vec[0]); // I want to call Derived1::zero_like(a) here
    

    Dann erzeuge eine statische Methode zero_like in der Klasse Base, welche dann eine protected virtuelle Funktion aufruft (wie von @hustbaer geschrieben, nur mit anderem Namen).

    Nur was hast du davon, wenn der Rückgabewert nur Base* ist, d.h. du also nur dessen Funktionen aufrufen kannst - oder aber alle anderen Funktionen müßten virtuell sein?!

    Bzw. warum soll überhaupt ein neues Objekt intern erzeugt werden, anstatt das vorhandene zu "nullen"?
    Ansonsten suche auch mal nach "Abstract Factory Pattern" oder auch "Virtual Copy Constructor".



  • @DocShoe sagte in Statische Memberfunktionen überschreiben:

    Geht das nicht viel einfacher mit einem Klassen-Template?

    Nochmal im Ernst, was genau möchtest du erreichen? Was sind Base/Derived konkret, und wie viele verschiedene "Derived" wird's geben? Und wie unterscheiden sie sich? Bisher hab ich den Eindruck, dass du eine Matrix-Klasse implementieren möchtest, mit Base als abstrakte Basisklasse und Derived als konkrete Implementierung mit int-Datentyp.



  • @Max3000 sagte in Statische Memberfunktionen überschreiben:

    Motiviert ist das ganze von Python Numpy, dort kann man sich Vektoren, Matrizen, etc. definieren und mit der zeros_like-Funktion Vektoren/Matrizen der gleichen Dimension aber mit 0 initialisiert erzeugen:

    Wie wäre es mit Templates ?

    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    
    template<typename T>
    class NumPyArray
    {
        std::vector<T> mData;       // Ich bin ein Freund von Vektoren
        size_t mSizeX, mSizeY;
    
    
    public:
        NumPyArray(size_t SizeX, size_t SizeY) :
            mSizeX(SizeX),
            mSizeY(SizeY),
            mData(SizeX * SizeY, 0)
        {        
        }
    
        NumPyArray(std::initializer_list<std::initializer_list<T>> ML)
        {
            mSizeX = 0;
            std::for_each(std::begin(ML), std::end(ML), [&](std::initializer_list<T> YL) {
                size_t CurXCount = 0;
    
                // Speichere Zeilen ab
                std::for_each(std::begin(YL), std::end(YL), [&](T X) {
                    mData.push_back(X);
                    CurXCount++;
                    });
                if (mSizeX == 0)
                    mSizeX = CurXCount;
                // Werfe Exception wenn zwei Zeilen unterschiedliche Längen haben
                if (mSizeX != CurXCount)
                    throw new std::exception("Fill Me");
                mSizeY++;
                });
        }
    
        NumPyArray zero_like() const
        {
            return NumPyArray(mSizeX, mSizeY);
        }
    };
    
    
    // C++17 wird benötigt
    int main(int argc, char** argv)
    {
        NumPyArray a{ { 1, 2, 3 }, {2, 3, 4}, {1, 2, 4}, {4, 5, 2} };
        NumPyArray b = a.zero_like();
    
        return 0;
    }
    


  • Hallo.

    @DocShoe sagte in Statische Memberfunktionen überschreiben:

    Nochmal im Ernst, was genau möchtest du erreichen? Was sind Base/Derived konkret, und wie viele verschiedene "Derived" wird's geben?

    Konkret soll es die Implementierung eines neuronalen Netzes werden. Mein Basis-Typ ist heißt "Layer" und definiert virtuelle Methoden (forward_propagate, backward_propagate, save_to_file, und alles was man sich so vorstellen kann). Abgeleitet davon gibt es VectorInputLayer (keine zusätzlichen Attribute), FullyConnectedLayer (speichert zusätzlich eine Gewichtsmatrix und einen Biasvektor), ConvolutionalLayer (speichert Kernelmatrix und Biaskonstante), FlatteningLayer (keine zusätzlichen Attribute), ... . Die Methoden der Basisklasse sollen dabei überschrieben werden, die Attribute der abgeleiteten Klassen sind aber sehr unterschiedlich. Die zeros_like-Funktion brauche ich um für jede dieser abgeleiteten Klassen einen Layer des selben Typs zu initialisieren bei denen alle Attribute (Gewichte, Bias, Kernel, ...) mit der passenden Größe, aber mit 0-Werten initialisiert werden. Die Vererbung erlaubt es mir dann im Hauptprogramm alle Layer unabhängig vom Typ durchzugehen und das Netz auszuwerten:

    std::vector<std::unique_ptr<Layer>> layers;  
    
    // Create Layer list and set weights, biases, activation function...
    //...
    
    // Evaluate network
    DataArray* data = layers.front()->eval(x);
    for(auto layer_it = layers.begin()+1; layer_it != layers.end(); ++layer_it)    
      (*layer_it)->forward_propagate(*data)
    

    Layer ist im oberen Beispiel Base und Derived1 ist zum Beispiel VectorInputLayer. Irgendwann muss ich mal einen Gradienten anlegen (ist selbst ein neuronales Netz mit gleicher Dimension), aber zunächst mit 0 initialisiert, also:

      NeuralNetwork grad;
    
      for(auto layer = net.layers.begin(); layer != net.layers.end(); ++layer)    
        grad.layers.push_back((*layer)->zeros_like());
    

    Ich habe zeros_like jetzt wie von anderen schon vorgeschlagen als normale Methode implementiert, die virtual in Layer und override in jeder abgeleiteten Klasse ist. Das funktioniert, ist aber nicht unbedingt die schönste Lösung.


    @Quiche-Lorraine sagte in Statische Memberfunktionen überschreiben:

    Wie wäre es mit Templates ?

    Danke für den Hinweis. Leider werden Templates bei mir nicht funktionieren, da die Klassen, für die ich zero_like brauche doch zu unterschiedlich sind und auch keinen Konstruktor mit gleicher Signatur haben.


    Ich danke allen nochmal für die Hilfe, ich denke ich habe jetzt die Lösung, die irgendwie funktioniert.

    Viele Grüße.


Anmelden zum Antworten