Dynamisches Array aus Klasse mit statischem Array vererben



  • Hallo,

    der Titel ist schlecht formuliert. Ich habe eine Klasse die ein statisches Array enthält. Einige Funktionen dieser Klasse greifen auf das Array zu. Ich brauche jedoch auch noch eine Variante der Klasse die ein dynamisches Array verwaltet. Der Zugriff auf das Array ist gleich, da in C++ sowieso jedes Array ein Pointer auf das erste Element ist.

    Ich würde daher gerne die Funktionen der statischen Klasse in der der dynamischen Klasse verwenden. Das funktioniert aber nicht, da die Methoden immer noch auf das statische Element zugreifen.

    Es sei dazu gesagt, dass ich hier extra ein Beispiel programmiert habe, dass keinen Sinn für sich selbst hat.

    Die einzige Lösung die mir einfällt ist es einen Pointer von dem Typ zu deklarieren und den schon im Interface zu implemenentieren. Ich wollte aber eigentlich Redundanz so weit wie möglich vermeiden. Gibt es da noch einen besseren Weg?

    template<typename _Ty>
    class Interface
    {
    public:
    	virtual operator _Ty* (void) = 0;
    
    	virtual _Ty summe(void) = 0;
    };
    
    template<typename _Ty, size_t _n>
    class Statisch : public Interface<_Ty>
    {
    private:
    	enum { _elCount = _n };
    	_Ty _array[_n > 0 ? _n : 1];
    
    protected:
    	enum { _elSize = sizeof(_Ty) };
    
    public:
    	Statisch(void) {
    		memset(this->_array, 0, _n * _elSize);
    	}
    
    	virtual _Ty summe(void) override 
    	{
    		_Ty Result = 0;
    		for( int i = 0; i < _elCount; i++ )
    			Result += this->_array[i];
    		return Result;
    	}
    
    	virtual operator _Ty* (void) override {
    		return this->_array;
    	}
    };
    
    template<typename _Ty>
    class Dynamisch : public Statisch<_Ty, 0>
    {
    private:
    	_Ty* _array;
    
    public:
    	Dynamisch(size_t n) : _array(new _Ty[n]()) {}
    
    	~Dynamisch(void) {
    		delete[] this->_array;
    	}
    };
    


  • Fällt wirklich niemandem etwas ein? Die von mir genannte Lösung würde ich ungern machen und ich bin sicher, dass es noch eleganter geht.



  • Was spricht gegen std::vector?


  • Mod

    Implementier deine Algorithmen mittels Iteratoren. Dann ist es egal, was da konkret hintersteht. Das ist auch der Grund, warum die STL das so macht. Was zu der naheliegenden Frage führt, warum du nicht gleich STL Container verwendest und das statische Array rauswirfst.



  • FrEEzE2046 schrieb:

    template<typename _Ty>
    

    👎 Bezeichner, die mit einem Unterstrich anfangen oder zwei aufeinanderfolgende Unterstruche besitzen, solltest Du nicht benutzen. Diese sind "reserviert".

    virtual operator _Ty* (void) = 0;
    

    👎 Schlechter Stil. Gib der Funktion einen vernünftigen Namen, wie getBase() oder sowas.

    class Dynamisch : public Statisch<_Ty, 0>
    

    👎 Warum von Statisch erben? Warum nicht direkt von Interface<_Ty>? Dann brauchst Du auch nicht diesen Hickhack mit dem _Ty _array[_n > 0 ? _n : 1]; .

    virtual _Ty summe(void) override
    

    👎 override gehört nicht zu C++

    Du verwendest das Wort "statisch" falsch. Im Kontext von Klassenelementen bedeutet statisch <-> nicht-statisch etwas anderes. Dir geht es darum, ob Objekte wirklich ("physikalisch") Elemente eines anderen Objekts sind oder nur dynamisch alloziert und per Zeiger referenziert werden ("logische Elemente"). "Statisch" ist nur die Array-Größe im ersten Fall.

    Ich wüsste jetzt nich, wozu man eine abstrakte Klasse dafür braucht. Nimm doch einfach zwei Zeiger, wenn Die Elemente immer hintereinander im Speicher liegen.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Schlechter Stil. Gib der Funktion einen vernünftigen Namen, wie getBase() oder sowas.

    Das ist ein Konvertierungsoperator, wofür also einen Namen festlegen?

    Sebastian Pizer schrieb:

    Warum von Statisch erben? Warum nicht direkt von Interface<_Ty>?

    Weil ich dann mehrfach die gleichen Methoden definieren muss, obwhol sie exakt gleich auf das array zugreifen und sowieso das selbe machen.

    Sebastian Pizer schrieb:

    override gehört nicht zu C++

    Ich verwende den VC 9.0. Da kann man explicit override verwenden, um zu vermeiden eine zweite virtuelle Methode zu erzeugen; ist also okay.



  • Also zum aktuellen C++ Standard gehört override nicht und meines Wissens auch nicht zum nächsten.

    Jedoch ist es ein Keyword bei C++/CLI! Nur mischen würde (ich wiederhole mich) nicht ohne guten Grund - und die guten Gründe sind meistens nicht so gut wie anfänglich gedacht!

    Simon



  • theta schrieb:

    Also zum aktuellen C++ Standard gehört override nicht und meines Wissens auch nicht zum nächsten.

    Ich wiederhole mich ebenfalls:
    Explicit Override ist eine Microsoft Spracherweiterung und bedingt keinen managed code.



  • FrEEzE2046 schrieb:

    theta schrieb:

    Also zum aktuellen C++ Standard gehört override nicht und meines Wissens auch nicht zum nächsten.

    Ich wiederhole mich ebenfalls:
    Explicit Override ist eine Microsoft Spracherweiterung und bedingt keinen managed code.

    zeig ma pls die quelle dieser behauptung...
    ich hab zumindest auf die schnelle nix in der msdn gefunden, was deine behauptung auch nur ansatzweise untermauern könnte^^

    bb



  • Jo, stimmt.
    http://msdn.microsoft.com/en-us/library/ksek8777.aspx

    Und erzeugt die Warnung (Level 4):

    warning C4481: nonstandard extension used: override specifier 'override'

    Simon



  • FrEEzE2046 schrieb:

    Sebastian Pizer schrieb:

    Schlechter Stil. Gib der Funktion einen vernünftigen Namen, wie getBase() oder sowas.

    Das ist ein Konvertierungsoperator, wofür also einen Namen festlegen?

    Dynamisch<int> quelle();
    
    int main() {
      int* zeiger = quelle();
      *zeiger = "Autsch!"[0];
    }
    

    FrEEzE2046 schrieb:

    Sebastian Pizer schrieb:

    Warum von Statisch erben? Warum nicht direkt von Interface<_Ty>?

    Weil ich dann mehrfach die gleichen Methoden definieren muss, obwhol sie exakt gleich auf das array zugreifen und sowieso das selbe machen.

    Ich denke, der Grund ist nicht gut genug. Vielleicht fällt Dir noch etwas eleganteres ein. Beispielsweise mit CRTP.

    FrEEzE2046 schrieb:

    Sebastian Pizer schrieb:

    override gehört nicht zu C++

    Ich verwende den VC 9.0. Da kann man explicit override verwenden, um zu vermeiden eine zweite virtuelle Methode zu erzeugen; ist also okay.

    habe ich mir schon gedacht. Es liegt aber wohl im Auge des Betrachters, ob es "okay" ist.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Dynamisch<int> quelle();
    
    int main() {
      int* zeiger = quelle();
      *zeiger = "Autsch!"[0];
    }
    

    Den Code verstehe ich nicht wirklich. Mit dem Konvertierungsoperator wäre int* zeiger = quelle möglich. Wofür aber "()" der Konstruktoraufruf?

    Was du mit Autsch meinst ist vermutlich, dass dieser Operator nicht überladen ist ???



  • theta schrieb:

    Jo, stimmt.
    http://msdn.microsoft.com/en-us/library/ksek8777.aspx

    ...This page is specific to ... .NET Framework...

    Daraufhin deuten auch ein paar andere Dinge wie z.B. "__interface" das definitiv Managed C++ ist.



  • FrEEzE2046 schrieb:

    Sebastian Pizer schrieb:

    Dynamisch<int> quelle();
    
    int main() {
      int* zeiger = quelle();
      *zeiger = "Autsch!"[0];
    }
    

    Den Code verstehe ich nicht wirklich. Mit dem Konvertierungsoperator wäre int* zeiger = quelle möglich. Wofür aber "()" der Konstruktoraufruf?

    Was du mit Autsch meinst ist vermutlich, dass dieser Operator nicht überladen ist ???

    "Gefährliches Halbwissen"-Alarm!

    Die erste Zeile ist eine Deklaration der Funktion quelle . Der Aufruf der Funktion in der 4. Zeile liefert ein temporäres Objekt, welches zu einem Zeiger "konvertiert" wird und dann mit dem Semikolon aufhört zu existieren. Der Zeiger "baumelt danach irgendwo rum". Die 5. Zeile stellt einen Schreibzugriff dar. "Autch!"[0] ist ein Lvalue-Ausdruck vom Typ const char mit dem Wert 'A'. Damit wollte ich darauf aufmerksam machen, dass diese Zuweisung eine ganz schlechte Idee ist; denn das Dynamisch<int> Objekt gibt es ja nicht mehr. Und das, obwohl der Code so harmlos aussieht.

    Aus diesem Grund bietet std::string auch keine Konvertierung nach const char* an. Dafür gibt es eine Elementfunktion, c_str .

    Da die Konvertierungsoperatoren schnell versehentlich aufgerufen werden können und in diesem Fall etwas liefern würden, was nicht unabhängig vom Quellobjekt ist, halte ich einen Konvertierungsoperator in diesem Fall für sehr unangebracht.

    Gruß,
    SP



  • asc schrieb:

    theta schrieb:

    Jo, stimmt.
    http://msdn.microsoft.com/en-us/library/ksek8777.aspx

    ...This page is specific to ... .NET Framework...

    Daraufhin deuten auch ein paar andere Dinge wie z.B. "__interface" das definitiv Managed C++ ist.

    Ja. Und trotzdem ist der override Sepcifier nicht (nur) managed. Probiers aus.
    Im MSDN sind übrigens fast alle Pages, auch wenn sie nichts mit .NET zu tun haben, .NET spezifisch!
    Simon



  • @Sebastian Pizer
    Sry, ich dachte bei "quelle" handelt es sich um eine Instanz von einem Objekt. Daher meine Fragen.
    Ich kann dich beruhigen, ich hatte überhaupt nicht vor einen soplchen Konvertierungsoperator zu verwenden. Ich habe überhaupt nicht vor, irgendetwas von meinem hier geposteten Code zu verwenden; er spiegelt lediglich mein "Problem" wider. CRTP werde ich mir anschauen und ggf. dahin tendieren.

    theta schrieb:

    Ja. Und trotzdem ist der override Sepcifier nicht (nur) managed. Probiers aus.
    Im MSDN sind übrigens fast alle Pages, auch wenn sie nichts mit .NET zu tun haben, .NET spezifisch!
    Simon

    Genau das wollte ich gerade erwähnen. Es ist handelt sich dabei um einen Fehler beim neuen Aufbau der MSDN. Auffallen sollte, dass es extra einen Link für managed code gibt, der wohl keinen Sinn machen würde, wenn die Site .NET spezifisch wäre.

    Zudem ist __interface auch in native C++ verwendbar. Es wird vorrangig für COM-Objekte verwendet.

    Ich könnte dass statische array ja auch per new (also dynamisch) allokieren, aber als statisch behandeln.
    Das hat aber zu radikalre Nachteile. Vor allem verbinde ich die Nachteile von statischer und dynamischer Allokation (unveränderbare Größe, Speicher im Heap).

    EDIT:
    Was haltet ihr von dieser Variante:

    template<typename _Ty>
    class Interface
    {
    public:
        virtual inline operator _Ty* (void) = 0;
        virtual inline _Ty summe(void) = 0;
    
    	typedef _Ty TYPE;
    };
    
    template<typename _Ty>
    class Implementation
    {
    public:
    	static inline _Ty summe(
    		_Ty*	array,
    		size_t n
    	)
    	{
    		_Ty Result = 0;
    		for( int i = 0; i < n; i++ ) {
    			Result += array[i];
    		}
    
    		return Result;
    	}
    };
    
    template<typename _Ty, size_t _n>
    class Statisch : public Interface<_Ty>
    {
    private:
    	enum { _elCount = _n };
        _Ty _array[_n > 0 ? _n : 1];
    
    protected:
        enum { _elSize = sizeof(_Ty) };
    
    public:
        Statisch(void) {
            memset(this->_array, 0, _n * _elSize);
        }
    
        virtual _Ty summe(void) override {
    		return Implementation<_Ty>::summe(this->_array, _elCount);
        }
    
        virtual operator _Ty* (void) override {
            return this->_array;
        }
    };
    
    template<typename _Ty>
    class Dynamisch : public Interface<_Ty>
    {
    private:
        _Ty* _array;
    	int _elCount;
    
    public:
        Dynamisch(size_t n) : _array(new _Ty[n]()) {
    		this->_elCount = n;
    	}
    
        ~Dynamisch(void) {
            delete[] this->_array;
        }
    
        virtual _Ty summe(void) override {
    		return Implementation<_Ty>::summe(this->_array, _elCount);
        }
    
        virtual operator _Ty* (void) override {
            return this->_array;
        }		
    };
    

    Somit spare ich mir zumindest die Redundanz für die Implementation. Es lässt sich evtl. mit der CRTP-Technik eine noch elegantere Variante entwickeln. Mir fehlt aber noch die Idee dazu.



  • Soll ich jetzt jede Sache, die mir vorher auch schon nicht gefallen hat und die Du nicht geändert hast, nochmal erwähnen? Nein, das spar ich mir jetzt.

    Deine Frage ist fast so sinnfrei wie "Was findet ihr besser, int oder long?". Viel interessanter ist, wofür Du das nutzen willst. Du willst eine Bewertung haben, ohne zu verraten, was Du damit anstellen willst. Man kann nur vermuten. Meine Vermutung ist: Du hast noch kein gutes C++ Sprachgefühl und verrenst Dich in "unschöne" Ansätze. Dann wäre es eigentlich umso wichtiger, zu verraten, was Du eigentlich anstellen willst.

    Gruß,
    SP



  • Ich halte nach wie vor nix davon, alles, was du da implementierst bietet dir STL/TR1 mit vector/array. Für die Summenbildung kann man accumulate benutzen, sodass du eigentlich nichst selbst programmieren musst.

    PS:
    Durch memset schränkst du deine verwendbaren Typen unnötigerweise auf PODs ein, std::fill ist da wesentlich flexibler.

    PPS:
    long ist immer besser. Wenn man irgendwas mehr bekommt kann das nicht schlechter sein 😉



  • Sebastian Pizer schrieb:

    Soll ich jetzt jede Sache, die mir vorher auch schon nicht gefallen hat und die Du nicht geändert hast, nochmal erwähnen? Nein, das spar ich mir jetzt.

    Deine Frage ist fast so sinnfrei wie "Was findet ihr besser, int oder long?". Viel interessanter ist, wofür Du das nutzen willst. Du willst eine Bewertung haben, ohne zu verraten, was Du damit anstellen willst. Man kann nur vermuten. Meine Vermutung ist: Du hast noch kein gutes C++ Sprachgefühl und verrenst Dich in "unschöne" Ansätze. Dann wäre es eigentlich umso wichtiger, zu verraten, was Du eigentlich anstellen willst.

    Was ich möchte habe ich doch eindeutig formuliert. Der Zugriff auf ein dynamisches und statisches Array mit dem selben Code; wenn ich es jetzt mal ganz simpel formulieren darf.



  • Dafür brauchst Du keine Laufzeit-Polymorphie und auch keine extra Klassen wie "Statisch" und "Dynamisch", wenn Du von einem linearen Speicherlayout ausgehst. Du kannst einfach Zeiger als "Iteratoren" benutzen.

    Du brauchst nicht einmal so eine Funktion "summe":

    #include <iostream>
    #include <vector>
    #include <numeric>
    
    void fill(std::vector<int> & v)
    {
      v.push_back(23);
      v.push_back(42);
    }
    
    int main()
    {
      static const int arr[] = {1,2,3,5,8};
      std::vector<int> dings;
      fill(dings);
      long s1 = std::accumulate(arr,arr+sizeof(arr)/sizeof(arr[0]),0L);
      long s2 = std::accumulate(dings.begin(),dings.end(),0L);
      std::cout << s1 << ", " << s2 << '\n';
    }
    

    std::accumulate ist ein Funktionstemplate. So kommt es mit beliebigen Iteratoren klar. Wenn Du Dich auf ein lineares Speicherlayout beschränkst, brauchst Du auch keine Templates. Wenn Du aber die Zeiger kapseln willst, kannst Du ja so etwas machen:

    #include <cassert>
    #include <cstddef>
    #include <boost/type_traits/integral_constant.hpp>
    #include <boost/type_traits/is_convertible.hpp>
    #include <boost/type_traits/is_same.hpp>
    #include <boost/type_traits/remove_cv.hpp>
    #include <boost/utility/enable_if.hpp>
    
    /// Ist die Zeiger-Konvertierung U* -> T* sicher, wenn man auch
    /// noch Zeigerarithmetik benutzen will?
    template<typename U, typename T>
    struct is_ptr_conversion_ok : boost::integral_constant<bool,(
         boost::is_convertible<U*,T*>::value
      && boost::is_same<typename boost::remove_cv<T>::type,
                        typename boost::remove_cv<U>::type>::value
    )> {};
    
    /// Teilansicht eines Arrays mit Referenz-Semantik
    template<typename T>
    class slice
    {
      T* beg_;
      T* end_;
    public:
      typedef std::size_t size_type;
      typedef T* iterator;
      typedef T& reference;
      typedef typename boost::remove_cv<T>::type value_type;
    
      slice() : beg_(0), end_(0) {}
      slice(T* b, T* e) : beg_(b), end_(e) {}
    
      template<typename U>
      slice(slice<U> const& src,
        typename boost::enable_if<is_ptr_conversion_ok<U,T> >::type* =0)
      : beg_(src.begin()), end_(src.end()) {}
    
      T* begin() const {return beg_;}
      T* end  () const {return end_;}
    
      std::size_t size() const {return end_ - beg_;}
      bool empty() const {return beg_==end_;}
    
      T& operator[](std::size_t index) const {
        assert( index < size() );
        return beg_[index];
      }
    };
    

    Gruß,
    SP


Log in to reply