std::vector: Konstruktor-Verhalten ändern



  • Hm... würde man das als "schöner" bezeichnen?

    typedef shared_ptr<handle_wrapper> hptr_t;
    
    class handle_wrapper {
    public:
    	/* some */
    	friend hptr_t create_socket();
    
    private:
    	handle_wrapper() {
    		/* some */
    	}
    	// ...
    };
    
    hptr_t create_socket() {
    	return hptr_t(new socket_handle);
    }
    
    // ...
    
    vector<sockptr_t> handles(5);
    for(sockptr_t& handle : handles) {
    	sh = create_socket();
    	cout << sh->get() << endl;
    }
    


  • Warum jetzt plötzlich shared_ptr ? Das brauchst du doch nicht.

    Ich hätte eher an sowas gedacht:

    handle_wrapper create_socket();
    
    int main()
    {
       std::vector<handle_wrapper> vector;
       std::generate_n(std::back_inserter(vector), 5, &create_socket);
    }
    


  • Das macht leider eine Copy-Operation?! Jedenfalls sind im vector dann die selben Handle-Werte.



  • Ad aCTa schrieb:

    Das macht leider eine Copy-Operation?! Jedenfalls sind im vector dann die selben Handle-Werte.

    Sofern du create_socket() so implementierst, dass es nicht jedes Mal dasselbe Objekt zurückgibt, sind die neuen Elemente im std::vector auch unterschiedlich.

    Die STL-Algorithmen std::generate() und std::generate_n() rufen die Generatorfunktion für jedes Element auf – etwa im Gegensatz zu std::fill() und std::fill_n() .



  • > Sofern du create_socket() so implementierst, dass es nicht jedes Mal dasselbe Objekt zurückgibt, sind die neuen Elemente im std::vector auch unterschiedlich.

    Das will ich auch meinen. 😉 Die Implementierung der Funktion ist trivial:

    inline socket_handle create_socket() {
    	return socket_handle();
    }
    

    Dabei kann es ja unmöglich immer das selbe Objekt sein?!



  • Ad aCTa schrieb:

    Dabei kann es ja unmöglich immer das selbe Objekt sein?!

    Wenn du meinen Rat befolgt hast und der Defaultkonstruktor immer die gleiche Initialisierung durchführt, doch. Ansonsten passe deine Wertsemantik an, dass

    socket_handle a;
    socket_handle b;
    assert(a == b);
    

    gilt (falls operator== nicht existiert, denk dir eine sinnvolle Implementierung für dieses Gedankenexperiment aus).

    Objekt-spezifische Initialisierungen sollten wie schon gesagt nicht im Defaultkonstruktor geschehen. Entsprechend müsste create_socket() auch mehr tun als nur Default- und Kopierkonstruktor aufzurufen.



  • Das ändert nichts. Selbst wenn ich die Semantik herstelle und

    inline socket_handle create_socket() {
    	socket_handle sh;
    	sh.init_();
    	return sh;
    }
    

    schreibe, bleiben die Handles gleich?!



  • Kannst du diesen Code kompilieren und sagen, wie bei dir die Ausgabe aussieht?

    #include <vector>
    #include <algorithm>
    #include <iterator>
    #include <iostream>
    
    int create_int()
    {
    	static int i;
    	return ++i;
    }
    
    void output(int i)
    {
    	std::cout << i << " ";
    }
    
    int main()
    {
       std::vector<int> vector;
       std::generate_n(std::back_inserter(vector), 5, &create_int);
       std::for_each(vector.begin(), vector.end(), &output);
    }
    


  • Logischerweise 1 2 3 4 5.



  • Gut, also ist deine Implementierung von generate_n() in Ordnung.

    Kannst du etwas mehr Code posten? Wie sehen Kopier- und Standardkonstruktor von socket_handle aus? Kannst du die Funktion create_socket() und die Stelle ihrer Verwendung (beim Füllen des Containers) zeigen?



  • vector<socket_handle> sockets;
    generate_n(std::back_inserter(sockets), 3, create_socket);
    for_each(sockets.begin(), sockets.end(), [](const socket_handle& sh) {
    	cout << sh.get() << endl;
    });
    
    // Kopierkonstruktor und Zuweisungsoperator sind compilergeneriert
    
    inline socket_handle create_socket() {
    	socket_handle sh;
    	sh.init_();
    	return sh;
    }
    
    struct socket_handle {
    	socket_handle() throw(std::runtime_error)
    		: used_(false), handle_(0)
    	{ }
    
    	bool used() const { return used_; }
    	~socket_handle() { /* Handle freigeben */ }
    	const SOCKET& get() const throw() { return handle_; }
    
    	friend socket_handle create_socket();
    
    private:
    	void init_() {
    		// Handle anfordern
    	}
    
    	bool used_;
    	SOCKET handle_;
    };
    

    Nicht mehr und nicht weniger.

    // EDIT:

    Au weia, jetzt fällt mir was ein. Da der Destruktor ja beim Beenden von create_socket aufgerufen wird, wird die Ressource freigegeben, sodass es beim nächsten Aufruf wieder die selbe Ressource ist, die ich dann bekomme. Scheinbar muss ich doch den Heap benutzen. 😞



  • So, ich glaube, in diesem Fall ist das gute alte Array immernoch die einfachste Lösung. 🙂



  • Ad aCTa schrieb:

    Au weia, jetzt fällt mir was ein. Da der Destruktor ja beim Beenden von create_socket aufgerufen wird, wird die Ressource freigegeben, sodass es beim nächsten Aufruf wieder die selbe Ressource ist, die ich dann bekomme. Scheinbar muss ich doch den Heap benutzen. 😞

    Ja, dann scheint das sinnvoll zu sein. Wäre ja auch blöd, wenn bei einer Vector-Reallokation plötzlich alle Ressourcen freigegeben würden. Daher immer schön schauen, dass man so oft wie möglich eine sinnvolle Wertsemantik hat, besonders bei Containern. 😉

    Ad aCTa schrieb:

    So, ich glaube, in diesem Fall ist das gute alte Array immernoch die einfachste Lösung. 🙂

    Wenigstens std::tr1::array läge also schon drin. 😃


Anmelden zum Antworten