Konstruktor eigener Klasse und std::vector Initialisierung - Verständnisproblem



  • Hallo :),

    entschuldigt den womöglich etwas seltsam anmutenden Threadtitel, aber mir fiel keine bessere Kurzbeschreibung zu folgender Sache ein. Ich poste erstmal den zu meinem Verständisproblem dazugehörigen (und für diesen Beitrag auf die wichtigsten Elemente gekürzten) Code.

    Hier die Klasse:

    // lokalitaet.h
    class Lokalitaet {
    public:
    	// Konstruktor
    	Lokalitaet(const std::string &l_name = "Neue Lokalitaet",
    			   const std::string &l_beschr = "Leer"):
    			   lok_name(l_name), lok_beschreibung(l_beschr)
    			   { lok_id = lok_nummer++; }
        // Operatoren
    	friend std::ostream& operator<<(std::ostream &os, const Lokalitaet &lok);
        // Getter, Setter, etc.
    private:
    	std::string lok_name;
    	std::string lok_beschreibung;
    	size_t lok_id;
    	static size_t lok_nummer;
    };
    

    Und die entsprechenden Definitionen:

    // lokalitaet.cpp
    #include "lokalitaet.h"
    
    // Staticelement
    size_t Lokalitaet::lok_nummer = 0;
    
    // Operatoren
    std::ostream& operator<<(std::ostream &os, const Lokalitaet &lok)
    {
    	os << lok.lok_name << "(" << lok.lok_id << "): " << lok.lok_beschreibung;
    	return os;
    }
    

    Wie man sieht soll der Konstruktorrumpf dafür sorgen, dass jedes "Lokalitaet"-Objekt seine eigene ID erhält. Wenn ich jetzt in die main() folgendes schreibe, klappt das auch wunderbar.

    //main.cpp
    #include "lokalitaet.h"
    /* weitere #include-Befehle, sowie using-Deklarationen */
    int main()
    {
        vector<Lokalitaet> orte;
    	orte.push_back(Lokalitaet("Test1", "abc"));
    	orte.push_back(Lokalitaet("Test2", "xyz"));
        // Ausgabe aller Vectorelemente
    	for(vector<Lokalitaet>::const_iterator it = orte.begin(); it != orte.end(); ++it)
    		cout << *it << endl;
    
    	return 0;
    }
    

    Wenn ich das ganze kompiliere erscheint auch die von mir erwartete Ausgabe (man beachte die verschiedenen ID's die in Klammern stehen).

    Test1(0): abc
    Test2(1): xyz
    

    Nachdem das erstmal alles geklärt ist kommt hier nun die Sache, die ich nicht so ganz verstehe, denn wenn die main() folgendermaßen aussieht:

    //main.cpp
    #include "lokalitaet.h"
    /* weitere #include-Befehle, sowie using-Deklarationen */
    int main()
    {
    	vector<Lokalitaet> orte(5, Lokalitaet("Test", "abc"));
        // Ausgabe aller Vectorelemente
    	for(vector<Lokalitaet>::const_iterator it = orte.begin(); it != orte.end(); ++it)
    		cout << *it << endl;
    
    	return 0;
    }
    

    Dann erscheint die für mich nicht nachvollziehbare folgende Ausgabe:

    Test(0): abc
    Test(0): abc
    Test(0): abc
    Test(0): abc
    Test(0): abc
    

    Jedes Vectorelement hat für mich unverständlicherweise die selbe ID (nämlich die Zahl die in Klammern steht, also die 0). Ich hoffe jemand könnte mir bitte erklären wieso beim anlegen eines nicht leeren Vectors der Konstruktor meiner Klasse offenbar nur einmal aufgerufen wird. Vielen Dank schonmal im voraus dafür und auch vielen Dank für das lesen meines doch etwas länger geratenen Beitrages :).



  • Also die Referenz erklärt dir das Problem meines Erachtens:
    http://www.cplusplus.com/reference/stl/vector/vector.html

    und dann das hier:

    explicit vector ( size_type n, const T& value= T(), const Allocator& = Allocator() );
    Repetitive sequence constructor: Initializes the vector with its content set to a repetition, n times, of copies of value.



  • Achso, also wird praktisch in meinem Fall ein "Lokalitaet"-Objekt erstellt und dieses dann n-mal kopiert. Ok, damit habe ich wieder was dazugelernt. Vielen Dank Firefighter :).



  • Genau das ist das Problem. Ich sitze jetzt auch schon ne Weile um das kopieren zu umgehen, aber ich weiß selber nicht wie und ob es überhaupt möglich ist, dein gewünschtes Verhalten trotz der Verwendung des Ctors wie du ihn gepostet hast in der Main. Vielleicht weiß da jemand anderes Rat.



  • Auch wenn es ich es nicht ganz so schön finde werde ich wohl erstmal die "Lokalitaet"-Objekte mittels push_back zum Vector hinzufügen, was jetzt auch nicht so schlimm ist, dennoch wäre auch ich an einer möglichen anderen Lösung interessiert :).



  • Du könntest den Kopierkonstruktor so überladen, dass auch Aufrufe von ihm in einer Erhöhung des Zählers resultieren.

    Lokalitaet::Lokalitaet(const Lokalitaet& Origin)
    : lok_name(Origin.lok_name)
    , lok_beschreibung(Origin.lok_beschreibung)
    , lok_id(lok_nummer++)
    {
    
    }
    

    Du musst dir allerdings auch überlegen, was du bei Zuweisungen machst. Entweder du verbietest sie ganz, was aber die Verwendung in Containern erschweren kann, oder du erhöhst auch dann die ID. Da innerhalb von Containern oft Kopien erzeugt werden können, ist die ID nicht mehr wirklich repräsentativ. Temporäre Objekte erhöhen den Counter ja ebenfalls. Darum kann es gut vorkommen, dass z.B. nach einer Reallokation von std::vector plötzlich zehn Nummern übersprungen werden.

    Eine relativ einfache Möglichkeit, eindeutige IDs zu bewahren, stellen Smart Pointer dar. boost::shared_ptr oder std::tr1::shared_ptr (identisch) kann in Containern verwendet werden und stellt sicher, dass bei Kopien kein neues Objekt entsteht. Siehe dazu auch diesen Artikel.



  • An Smart Pointer hatte ich noch gar nicht gedacht und das obwohl ich dazu bereits einiges im "C++ Primer" erfahren hatte. Es wird wohl mal Zeit mich intensiver damit auseinanderzusetzen. Vielen Dank Nexus für die Hilfestellung :).


Anmelden zum Antworten