Forum: PC-Programmierung Socket in Map speichern (C++)


von Asio (Gast)


Lesenswert?

Hallo,

ich versuche seit gestern erfolglos für boost asio ein tcp::socket in 
einer std::map zu speichern.
1
std::map<int, tcp::socket> socket_map;
1
void session()
2
{
3
   tcp::socket s(ioservice);
4
   socket_map.emplace(123,std::move(s));
5
   acceptor.accept(socket_map[123]);
6
}
Fehler: "Es wurde versucht, auf eine gelöschte Funktion zu verweisen. 
with [Protocol=boost::asio::ip::tcp"]

1
void session()
2
{
3
   tcp::socket s(ioservice);
4
   socket_map.insert(std::make_pair(123, std::move(s)));
5
   acceptor.accept(socket_map[123]);
6
}
Fehler: kein geeigneter Standardkonstruktor verfügbar with 
[Protocol=boost::asio::ip::tcp"]

Ich bin nicht mal sicher ob das move unbedingt nötig ist, tcp::socket 
ist wohl zwar nicht kopierbar aber ich möchte "einfach" nur ein Socket 
in einer map erzeugen/speichern.

Hat jemand eine Idee wie man den Socket in die Map bekommt?
Viele Grüße

von Dr. Sommer (Gast)


Lesenswert?

Die ganze Fehlermeldung zu zeigen, oder vielleicht den Namen der 
Funktion auf die zugegriffen werden soll geht nicht?

von Felix U. (ubfx)


Lesenswert?

Asio schrieb:
>
1
void session()
2
> {
3
>    tcp::socket s(ioservice);
4
>    socket_map.insert(std::make_pair(123, std::move(s)));
5
>    acceptor.accept(socket_map[123]);
6
> }
> Fehler: kein geeigneter Standardkonstruktor verfügbar with
> [Protocol=boost::asio::ip::tcp"]

Also mit VC++ funktioniert das. Macht aber natürlich wenig Sinn, ein 
Objekt im lokalen Kontext zu erstellen und dann in den pair Konstruktor 
zu moven, wodurch das Objekt ungültig wird. Er versucht dann, das Objekt 
neu mit einem Standardkonstruktor zu konstruieren und das schlägt 
natürlich fehl. Versuch mal
1
map.insert(std::make_pair(123, tcp::socket(ioservice)));
oder ansonsten (unique) pointer.

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Versuche mal das boost ip::tcp::socket objekt auf dem heap zu 
alloziieren, und verwende shared pointer um das kopieren zu vermeiden 
ohne aufwand mit der Speicherverwaltung zu bekommen. Ungetestet:
1
std::map<int, shared_ptr<tcp::socket>> socket_map;
2
void session(){
3
  shared_ptr<tcp::socket> socket(new tcp::socket(ioservice));
4
  socket_map[123] = socket;
5
  acceptor.accept(*socket);
6
}

von Dr. Sommer (Gast)


Lesenswert?

Felix U. schrieb:
> Er versucht dann, das Objekt
> neu mit einem Standardkonstruktor zu konstruieren
Wenn das so wäre, wären alle nicht-kopierbaren Objekte ja auch nicht 
move-bar. Der move-Konstruktor ruft nicht den Standardkonstruktor auf, 
sondern hinterlässt das Original-Objekt in einem "leeren" Zustand 
(typischerweise einer, sodass der Destruktor gar nichts tut).

Daniel A. schrieb:
> verwende shared pointer um das kopieren zu vermeiden
Eine Socket-Klasse enthält vermutlich nur einen Integer. Den zu kopieren 
geht schneller als eine Heap-Allokation. Insbesondere der Move 
Construktor ist ja darauf ausgelegt, möglichst schnell zu sein. Und dann 
auch noch shared_ptr, der nochmal extra langsam ist?! Da doch wenigstens 
unique_ptr.

von Felix U. (ubfx)


Lesenswert?

Dr. Sommer schrieb:
> Der move-Konstruktor ruft nicht den Standardkonstruktor auf

Das sollte so sein, und in VC++ funktioniert es ja auch. Das ist aber 
die einzige logische Erklärung für mich, warum er sich an der Stelle 
über einen gelöschten Standardkonstruktor beschweren könnte.

von Dr. Sommer (Gast)


Lesenswert?

Felix U. schrieb:
> Das ist aber
> die einzige logische Erklärung für mich, warum er sich an der Stelle
> über einen gelöschten Standardkonstruktor beschweren könnte.
Es wird ja nichtmal klar gesagt, welcher Standardkonstruktor hier 
gemeint ist. Es ist auch nicht gesagt, dass überhaupt die gezeigte 
Stelle relevant ist. Es kann gut sein, dass std::map irgendwo noch 
Instanzen anlegt. Aber ohne vollständige Fehlermeldung kann man dazu nix 
sagen. Ist hier ja so üblich, mit Informationen zu geizen.

Hier ein Beispiel: https://ideone.com/XgnGwm
Der Standard-Konstruktor von X existiert nicht und wird auch nicht 
aufgerufen. Es wird nur beim moven der Move-Konstruktur aufgerufen, und 
2x der Destruktor. Angenommen das m_x ist der Socket-File-Handle, dann 
könnte man den im Destruktor schließen sofern es nicht -1 ist.

von Daniel A. (daniel-a)


Lesenswert?

Dr. Sommer schrieb:
> Daniel A. schrieb:
>> verwende shared pointer um das kopieren zu vermeiden
> Eine Socket-Klasse enthält vermutlich nur einen Integer.

Das ist Spekulation zur Implementation einer API. Boost enthält hier 
noch etwas mehr als nur dass, aber diese Implementationsdetails sind 
irrelevant. Shared Pointer sind auch nur Reference Counting und das 
Kopieren eines Pointers, was immernoch sehr schnell geht. Insgesamt wäre 
es also verfrühte Optimierung mit ungewissem Ausgang, wenn es den ginge.

> Den zu kopieren geht schneller als eine Heap-Allokation.
> Insbesondere der Move Construktor ist ja darauf ausgelegt,
> möglichst schnell zu sein.

Die API hat hier aber keinen copy constructor und ist auch nicht 
moveable, dein Vorschlag ist somit garnicht umsetzbar und damit 
irrelevant. Das Objekt zu Kopieren wäre ohnehin ein kaputtes Design, da 
es vom selben Socket logisch gesehen immer nur eine Instanz geben kann. 
Ein gutes Programmdesign ist wichtiger, als ein paar theoretisch 
womöglich verlorene CPU Zyklen, insbesondere hier wo es nicht ins 
Gewicht fällt.

von Mapper (Gast)


Lesenswert?

Das Problem hier ist der
std::map<int, tcp::socket>::operator[].
Mit find() funktioniert es.

von Asio (Gast)


Lesenswert?

Hallo danke,

Irgendwie sind nun die Fehler bei beiden Varianten identisch da hatte 
ich einen Fehler gemacht also bei der Variante emplace und der Variante 
insert tritt folgender identischer Fehler auf:
1
C:\Visual Studio 2015.3\VC\include\tuple:1180: Fehler: C2512: "boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<Protocol>>::basic_stream_socket": Kein geeigneter Standardkonstruktor verfügbar
2
with
3
[
4
    Protocol=boost::asio::ip::tcp
5
]

Und der gleiche Fehler auch bei der Variante Felix U. (ubfx):
1
map.insert(std::make_pair(123, tcp::socket(ioservice)));


Die zwei Möglichkeiten mit emplace und insert hatte ich von hier aus der 
Antwort: 
https://stackoverflow.com/questions/31594459/problems-with-map-container

Hier noch ein Link zum socket:
http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/reference/ip__tcp/socket.html

hier ein Link zu move: 
http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/overview/cpp2011/move_objects.html


Aber das Endziel ist ja die Sockets einfach global in eine Map zu 
bekommen, also es muss nicht verschoben werden (den Ansatz hatte ich nur 
aus Not begonnen) am besten natürlich einfach erzeugt in der Map.


Die Variante Daniel Abrecht (daniel-a):
1
std::map<int, shared_ptr<tcp::socket>> socket_map;
2
void session(){
3
  shared_ptr<tcp::socket> socket(new tcp::socket(ioservice));
4
  socket_map[123] = socket;
5
  acceptor.accept(*socket);
6
}
Das funktioniert, allerdings bekomme ich dann bei anderen Funktionen 
eine Fehlermeldung:
1
socket_map[123].available();
1
C2039 available ist kein Element von "std::shared_ptr...
1
socket_map[123].shutdown();
1
C2039 shutdown ist kein Element von "std::shared_ptr...
1
socket_map[123].close();
1
C2039 close ist kein Element von "std::shared_ptr...

LG

von guest (Gast)


Lesenswert?

Asio schrieb:
> socket_map[123].available();

Da sind ja jetzt auch Pointer in Deiner Map, also eher so:
1
socket_map[123]->available();
Analog dann auch bei den anderen.

von Mapper (Gast)


Lesenswert?

Ich wiederhole:
Der Fehler ist, daß Du via operator[] auf die Sockets in der Map 
zugreifst.

von Daniel A. (daniel-a)


Lesenswert?

Asio schrieb:
> Das funktioniert, allerdings bekomme ich dann bei anderen Funktionen
> eine Fehlermeldung:
> socket_map[123].available();
> C2039 available ist kein Element von "std::shared_ptr

Man muss den Pointer dann vor der Verwendung noch Dereferenzieren, also 
-> statt .

von guest (Gast)


Lesenswert?

Daniel A. schrieb:
> Die API hat hier aber keinen copy constructor und ist auch nicht
> moveable,

Moveable sind sie schon:
http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/reference/basic_stream_socket/basic_stream_socket/overload5.html

von guest (Gast)


Lesenswert?

Asio schrieb:
> Ich bin nicht mal sicher ob das move unbedingt nötig ist, tcp::socket
> ist wohl zwar nicht kopierbar aber ich möchte "einfach" nur ein Socket
> in einer map erzeugen/speichern.
>
> Hat jemand eine Idee wie man den Socket in die Map bekommt?

Wozu willst Du das überhaupt tun? Wenn Dein Funktionsname korrekt ist 
("session"), ist der Socket am Ende der Funktion sowieso nutzlos, also 
warum ihn erst noch in irgendeine Map moven?

Ansonsten könnte die von Mapper bemängelte Verwendung des [] Operators 
eventuell funktionieren, wenn man der Map noch einen eigenen Allokator 
spendierd. Allerdings (Zitat MS): "Writing and using your own allocator 
class is an advanced C++ topic." :)

von Asio (Gast)


Lesenswert?

Mit -> statt . klappt nun alles

Nochmal ganz naiv gefragt, da ich mit shared_ptr noch nichts gemacht 
hatte:
Wenn nun das in einer Funktion steht:
1
shared_ptr<tcp::socket> socket(new tcp::socket(ioservice));
2
socket_map[123] = socket;
Ist dann der Socket auch nach verlassen der Funktion noch vorhanden (in 
der Map) oder nur ein Zeiger auf den ehemaligen Ort in der Funktion?

Aus Interesse, gibt es auch eine Lösung bei der "ganz normal" die 
Asiodinge ohne * und -> angesprochen werden?

Mapper schrieb:
> Mit find() funktioniert es.
Wie kann man mit find() ein Socket in der Map erzeugen?

guest schrieb:
> Wozu willst Du das überhaupt tun? Wenn Dein Funktionsname korrekt ist
> ("session"), ist der Socket am Ende der Funktion sowieso nutzlos, also
> warum ihn erst noch in irgendeine Map moven?

Eine Serveranwendung soll eine Kommunikation zwischen Clients 
ermöglichen die nach Verbindungsabbruch per gemeinsamer ID über den 
Server wiederhergestellt werden kann.
Die read() und write() Funktionen für die unterschiedlichen Sockets 
sollen also in gleichen Funktionen/Threads nutzbar sein (um dort zu 
kommunizieren) und sich abwechseln können, zB der Server erhält Daten 
per read(socket_A) von Client und sendet an anderen Client 
write(socket_B).

von Mapper (Gast)


Lesenswert?

Asio schrieb:
> Mapper schrieb:
>> Mit find() funktioniert es.
> Wie kann man mit find() ein Socket in der Map erzeugen?

Die Frage verstehe ich nicht. Natürlich kann man das nicht.

Mein Hinweis bezog sich auf deinen ursprünglichen Code wo der Fehler in 
der Zeile

> acceptor.accept(socket_map[123]);

lag.

Wie man einen Socket in der Map erzeugt zeigst du doch selber wunderbar 
in der Zeile darüber.

von Asio (Gast)


Lesenswert?

Mapper schrieb:
> Mein Hinweis bezog sich auf deinen ursprünglichen Code wo der Fehler in
> der Zeile

verstanden

Mapper schrieb:
> Wie man einen Socket in der Map erzeugt zeigst du doch selber wunderbar
> in der Zeile darüber.

Das was ich zeige funktioniert ja allerdings nicht und erzeugt den 
genannten Fehler.
Funtionieren tut nur die Variante mit shared_ptr von Daniel Abrecht wo 
man dann mit * und -> arbeiten muss.

von Mapper (Gast)


Lesenswert?

Asio schrieb:
> Mapper schrieb:
>> Wie man einen Socket in der Map erzeugt zeigst du doch selber wunderbar
>> in der Zeile darüber.
>
> Das was ich zeige funktioniert ja allerdings nicht und erzeugt den
> genannten Fehler.
Doch es funktioniert. Der Fehler ist in der Zeile darunter.

> Funtionieren tut nur die Variante mit shared_ptr von Daniel Abrecht wo
> man dann mit * und -> arbeiten muss.
Das funktioniert, weil std::shared_ptr einen default constructor hat.

von Heiko L. (zer0)


Lesenswert?

Das Problem ist, dass der []-operator ggf. ein neues Objekt in die Map 
einfügt, weil er immer eine Referenz zurückgibt. Ist kein Eintrag für 
[123] vorhanden, erzeugt die map ein neues Objekt und liefert eine 
Referenz darauf zurück. Das kann dann auch mit Zeigern ggf. ein nicht 
gültiger sein, so dass eine unbesehene Benutzung dann abschmirgelt.
Fazit: Du willst mit find suchen, schauen dass der iterator nicht 
map.end() ist, und nur dann ggf. dereferenzieren.

von Sebastian V. (sebi_s)


Lesenswert?

Asio schrieb:
> Ist dann der Socket auch nach verlassen der Funktion noch vorhanden (in
> der Map) oder nur ein Zeiger auf den ehemaligen Ort in der Funktion?

Der Socket ist nach der Funktion dann noch in der Map vorhanden. Das ist 
ja das praktische bei den Smart Pointern. Bei shared_ptr wird bei jeder 
Kopie die du von dem Pointer erzeugst ein Zähler hochgezählt. Nachdem du 
den Pointer in die Map kopiert hast ist der Zähler bei 2. Wird ein 
shared_ptr Objekt zerstört, wie das lokale Objekt am Ende der Funktion, 
wird der Zähler um eins verringert. Erst wenn der Zähler auf 0 ist wird 
das eigentlich gespeicherte Objekt, also hier der Socket, gelöscht.

: Bearbeitet durch User
von Asio (Gast)


Lesenswert?

Fantastisch gute Sache! Jetzt hab ich erst geschnallt das der Fehler vom 
Zugriff gekommen ist denn der Compiler hat bei mir keine explizite 
Markierung im Code gezeigt. Dann ist die Variante von Felix U doch 
effektiv:

Direktes Erstellen:
map.insert(std::make_pair(123, tcp::socket(ioservice)));

Zugriff:
map.find(123)->second

Sehr schöne Sache.


Heiko L. schrieb:
> schauen dass der iterator nicht
> map.end() ist, und nur dann ggf. dereferenzieren.

map.end() zum durschleifen der Keys ber ok? So schleife ich in der Regel 
die Keys durch:
for (auto i(map.begin()); i != map.end(); ++i) {key=i->first}

von Sebastian V. (sebi_s)


Lesenswert?

Asio schrieb:
> Zugriff:
> map.find(123)->second

Ich würde eher zu

map.at(123)

raten. Das ist fast wie die Variante über map[123], werden hier keine 
neuen Elemente angelegt weshalb es wie bei find auch keinen Fehler geben 
sollte. Im Gegensatz zu find gibt es allerdings eine exception, wenn du 
versuchst auf ein ungültiges Element zuzugreifen, statt undefiniertes 
Verhalten. Dazu ist es auch weniger Tipparbeit (kein ->second 
notwendig).

Asio schrieb:
> map.end() zum durschleifen der Keys ber ok? So schleife ich in der Regel
> die Keys durch:
> for (auto i(map.begin()); i != map.end(); ++i) {key=i->first}

So ist es gedacht. Beachte, dass in dem Fall wo i == map.end() ist die 
Schleife abgebrochen wird und nicht nochmal versucht wird über i->first 
auf irgendwelche Daten zuzugreifen. Der Iterator der gleich map.end() 
ist, enthält nämlich keine Daten mehr und zeigt nur das Ende an.

von tictactoe (Gast)


Lesenswert?

Asio schrieb:
> Direktes Erstellen:
> map.insert(std::make_pair(123, tcp::socket(ioservice)));

Das müsste eigentlich auch gehen, und weniger kopieren/verschieben:
1
map.emplace(123, tcp::socket(ioservice));

Asio schrieb:
> So schleife ich in der Regel die Keys durch:
> for (auto i(map.begin()); i != map.end(); ++i) {key=i->first}

Der moderne C++er macht's so:
1
for (auto& it: map) {   // it steht für "item", nicht für "iterator"
2
   // falls ein eigener Name für den Key gewünscht ist:
3
   const auto& key = it.first;
4
   // ...
5
}

von Asio (Gast)


Lesenswert?

Perfekt gleich an drei Stellen verbessert, das wird ja immer besser, 
besten Dank.

Beitrag #5158345 wurde von einem Moderator gelöscht.
Beitrag #5158697 wurde von einem Moderator gelöscht.
Beitrag #5159286 wurde von einem Moderator gelöscht.
Beitrag #5159538 wurde von einem Moderator gelöscht.
Beitrag #5160089 wurde von einem Moderator gelöscht.
Beitrag #5160646 wurde von einem Moderator gelöscht.
Beitrag #5161135 wurde von einem Moderator gelöscht.
Beitrag #5161268 wurde von einem Moderator gelöscht.
Beitrag #5161591 wurde von einem Moderator gelöscht.
Beitrag #5161593 wurde von einem Moderator gelöscht.
Beitrag #5165583 wurde von einem Moderator gelöscht.
Beitrag #5165587 wurde von einem Moderator gelöscht.
Beitrag #5165588 wurde von einem Moderator gelöscht.
Beitrag #5165839 wurde von einem Moderator gelöscht.
Beitrag #5166150 wurde von einem Moderator gelöscht.
Beitrag #5167765 wurde von einem Moderator gelöscht.
Beitrag #5167785 wurde von einem Moderator gelöscht.
Beitrag #5167797 wurde von einem Moderator gelöscht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.