Wird bei der Arrayinitialisierung der Konstruktor des Arraytyps aufgerufen?



  • Hallo,

    ich habe ein Verständnisproblem bezüglich der korrekten Initialisierung von Arrays.
    Momentan sitze ich an einer Projektarbeit fürs Studium. Im Endeffekt sollen wir einen Compiler bauen, ohne dabei die std-lib zu verwenden. Da wir bei dem Projekt somit nur Basistypen verwenden dürfen, müssen wir Manche (z.B. vector) selbst implementieren.

    Ich habe versucht die vector-Klasse grob zu implementieren, damit einfaches Listen-handling verfügbar ist und man nicht ständig seine Arrays manuell vergrößern muss.

    Ich habe also meine Klasse Vector:

    template <class T>
    class vector
    {
    public:
        vector();
        vector(int initialSize);
    
        ...
    
    private:
        void resize();
    
        T buffer[];
        int index;
        int maxSize;
    
    };
    

    Mein Problem liegt hier im lokalen Puffer (buffer Variable). Wenn ich diese neu initialisiere (z.B. beim Vergrößern)

    buffer = new T[initialSize];
    

    erhalte ich eine Fehlermeldung vom g++ compiler:

    no matching function for call to ‘Transition::Transition()’

    Dabei ist Transition die Klasse, auf die sich der Vektor bezieht (also jetzt nicht mehr generisch). Jedenfalls hat die Transition-Klasse keinen prameterlosen Konstruktor und dass ich hier diese Fehlermeldung erhalte, lässt mich darauf schließen, dass bei der Initialisierung von Arrays für jedes Element erst mal der Standardkonstruktor (damit meine ich den Parameterlosen) aufgerufen wird.
    Ist das soweit korrekt? Ich dachte nämlich bisher dass einfach nur der nötige Speicher reserviert wird und sonst nichts passiert.

    Falls obige Vermutung richtig ist, würde es dann Sinn machen den Puffer zu einem Pointer-Array zu machen, damit nicht versucht wird den Konstruktor der Zielklasse aufzurufen?
    Also statt

    T buffer[];
    

    dann

    T (*buffer)[];
    


  • Ja der Konstruktor wird immer aufgerufen. Bei einem std::vector kannst du auch kein resize machen wenn T keinen standard Konstruktor hat. Schau dir mal std::aligned_stotage und placement new an.



  • Statt deine Vectorklasse anders zu gestalten, als man es von Arrays oder std:vector gewohnt ist, kannst du ja auch statt

    dein::vector<Transition>

    dein::vector<Transition*>

    nutzen.



  • Oder vector<boost::optional<T>>

    sebi707 schrieb:

    Ja der Konstruktor wird immer aufgerufen. Bei einem std::vector kannst du auch kein resize machen wenn T keinen standard Konstruktor hat. Schau dir mal std::aligned_stotage und placement new an.

    Naja es gibt
    void resize( size_type count);
    und
    void resize( size_type count, const value_type& value );

    Der erste Overload geht freilich nicht ohne Default-Ctor.
    Für den zweiten reicht ein Copy-Ctor.



  • Deshalb holt sich der echte std::vector ja auch einen unitialisierten Speicherbereich via ::operator new (indirekt, durhc Alloc) und ruft erst bei Bedarf den CTor auf.



  • Nathan schrieb:

    Deshalb holt sich der echte std::vector ja auch einen unitialisierten Speicherbereich via ::operator new (indirekt, durhc Alloc) und ruft erst bei Bedarf den CTor auf.

    Wann sieht der echte Vector den Bedarf den CTor nicht aufzurufen?
    Im folgenden Code wird er 5x aufgerufen. Ich bin der Meinung das er immer aufgerufen wird, sobald Speicher neu-allokiert wird.

    class MyClass {
    public:
      MyClass() {
        std::cout << "C'tor called" << std::endl;
      }
    
      ~MyClass() {
      }
    };
    
    int main() {
      std::vector<MyClass> v(5);
    }
    


  • jb2603 schrieb:

    Wann sieht der echte Vector den Bedarf den CTor nicht aufzurufen?

    Wenn man nur reserve'd und nicht resize'd.



  • Vielen Dank Leute. Hat mir sehr geholfen.

    Die Idee vector<Transition*> zu benutzen ist genial - da hätte ich mal selber drauf kommen können 😃 - danke dafür.

    Kann mir vielleicht noch einer den Nutzen dahinter erklären, also warum der Ctor aufgerufen wird? Dient das nur dem Zweck den Speicher nicht undefiniert zu lassen falls man das Objekt abfragt bevor überhaupt eins erzeugt wurde?


  • Mod

    Code4Fun schrieb:

    Dient das nur dem Zweck den Speicher nicht undefiniert zu lassen falls man das Objekt abfragt bevor überhaupt eins erzeugt wurde?

    Du beschreibst das, als wäre das nur eine kleine Unannehmlichkeit, wenn das passieren würde.



  • Code4Fun schrieb:

    Kann mir vielleicht noch einer den Nutzen dahinter erklären, also warum der Ctor aufgerufen wird? Dient das nur dem Zweck den Speicher nicht undefiniert zu lassen falls man das Objekt abfragt bevor überhaupt eins erzeugt wurde?

    Wenn kein Ctor aufgerufen wird dann gibt es das Objekt schlicht und ergreifend nicht. Ob der Speicher reserviert wurde spielt dabei keine Rolle -- ein Stückchen Speicher wird halt nicht einfach dadurch zu einem Objekt einer bestimmten Klasse indem man es sich wünscht. Oder einen Zeiger diesen Typs auf den Anfang dieses Speichers erzeugt.

    Und wenn man auf ein Objekt zugreift das nicht existiert, dann ist das ein ganz grober Fehler.

    Und das gilt auch wenn der erste Zugriff etwas wie z.B. der operator = ist.

    Also...
    ANGENOMMEN dass vector::resize KEINEN Konstruktor aufrufen würde, dann wären BEIDE Verwendungen im Beispiel unten FALSCH:

    void Foo()
    {
        vector<string> v1;
        v1.resize(123);
        size_t s1 = v1[0].size(); // Fehler
    
        vector<string> v2;
        v2.resize(123);
        v2[0] = std::string("foo"); // AUCH ein Fehler
    }
    


  • Code4Fun schrieb:

    Ist das soweit korrekt? Ich dachte nämlich bisher dass einfach nur der nötige Speicher reserviert wird und sonst nichts passiert.

    Selber testen wäre höchst angebracht gewesen.
    https://ideone.com/vxAPu9

    #include <iostream>
    using namespace std;
    
    struct Tester{
    	Tester(){
    		cout<<"hello, world!\n";
    	}
    };
    
    int main(){
    	Tester array[10];
    }
    


  • sebi707 schrieb:

    Ja der Konstruktor wird immer aufgerufen.

    Kann man jetzt so auch nicht sagen 🙂

    A* pa = (A*)::operator new(sizeof(A));
    

  • Mod

    Mr.Long schrieb:

    sebi707 schrieb:

    Ja der Konstruktor wird immer aufgerufen.

    Kann man jetzt so auch nicht sagen 🙂

    A* pa = (A*)::operator new(sizeof(A));
    

    Und was hast du hier, außer einem Zeiger auf rohen Speicher?



  • A* pa = cast<A*>(4711);
    

    aber das Zeigern auf toten Speicher hat wenig mit der Frage zu tun.



  • Arcoth schrieb:

    Mr.Long schrieb:

    A* pa = (A*)::operator new(sizeof(A));
    

    Und was hast du hier, außer einem Zeiger auf rohen Speicher?

    Ich bin mir immer noch nicht sicher was der TO überhaupt möchte. Geht es um die Programmierung eines eigenen vector der nicht automatisch den Konstruktor aufruft oder geht es um die internen Strukturen des vectors? Also wenn der vector seinen Speicherbereich vergrößert und einige Elemente am Ende noch nicht belegt sind? Im letzteren Fall wäre Speicher mit ::operator new reservieren und dann nur die benötigten Objekte mit placement new erzeugen, das was man meiner Meinung nach tun sollte.



  • Arcoth schrieb:

    Mr.Long schrieb:

    sebi707 schrieb:

    Ja der Konstruktor wird immer aufgerufen.

    Kann man jetzt so auch nicht sagen 🙂

    A* pa = (A*)::operator new(sizeof(A));
    

    Und was hast du hier, außer einem Zeiger auf rohen Speicher?

    Hängt von der Implementierung des operators new ab 😃


Log in to reply