template frage
-
Hey,
ich habe eine Klasse basic_socket, die als Member einen Socket-Deskriptor hat und diesen mit socket() im Konstruktor initialisiere.
Da ich sowohl TCP-, als auch UDP-Sockets erstellen können möchte muss dem Konstruktor momentan noch ein Parameter übergeben werden anhand dessen dann in socket() entweder SOCK_STREAM oder SOCK_DGRAM übergeben wird.
Das würde ich gerne eleganter mit Templates lösen. Also z.B. basic_socket<TCP>.
Wie mache ich das am besten? Ich bräuchte ja theoretisch noch 2 extra Klassen als Typen - das wäre aber wieder zusätzlicher Overhead. Ist das überhaupt praktikabel, oder soll ich die Übergabe als Parameter beibehalten?
-
Ich bräuchte ja theoretisch noch 2 extra Klassen als Typen
Da nutzt man eher eine Enumeration.
Wenn die Information zur Compile-Zeit bekannt ist, nimmst du natürlich Templates.
-
template_nooob schrieb:
Ich bräuchte ja theoretisch noch 2 extra Klassen als Typen - das wäre aber wieder zusätzlicher Overhead.
Overhead? Du meinst 1-2 Mikrosekunden mehr Compilezeit?
Was du hier vor hast, ist eine stark abgespeckte Version von:
http://en.wikipedia.org/wiki/Policy-based_designOder einfacher:
template <int socket_type> class basic_socket { ... }; typedef basic_socket<SOCK_STREAM> TCP_socket; typedef basic_socket<SOCK_DGRAM> UDP_socket;
-
Loesungen gibt es viele, ich wuerde hier Named Contructor Idiom bevorzugen. Nur weil ein einzelner Parameter anders ist, wuerde ich es nicht im Typen nach aussen tragen.
Das würde ich gerne eleganter mit Templates lösen
Nein, ist es nicht, wobei es stark vom Rest der Klasse und der Verwendung abhaengt.
-
Was ich mal gemacht habe (wobei bei mir die einzelnen Protokolle auch noch Templates waren, für IPv4 oder IPv6 (oder Dual Stack, also beides)):
struct protocol_base { typedef socket_t /* systemspezifisch */ ... }; struct tcp : protocol_base { typedef address_t /* systemspezifisch */ ... }; struct udp : protocol_base { ... }; ... // structs für andere Protokolltypen template <typename Protocol> class socket_interface { }; template <> class socket_interface<tcp> { public: void send(...); // TCP-send void connect(...); // kann nur TCP ... }; template <> class socket_interface<udp> { public: void send(...); // udp-send void recv(...); ... }; template <typename Protocol> class basic_socket : socket_interface<Protocol> { ... }; typedef basic_socket<tcp> tcp_socket; typedef basic_socket<udp> udp_socket; tcp_socket s; s.connect(...); udp_socket ss; ss.connect(...); // outch, interface nicht veerbtAllerdings muss man u.U. Tipp 43 aus Effective C++ beachten.
Keine Ahnung, ob das sonderlich praktisch in allen Lebenslagen ist, aber ich fand, es war mal eine ganz nette Alternative zu virtuellen Funktionen in meiner Netzwerklibrary und je nach Templateparameter gibt es auch andere Memberfunktionen (z.B. kein connect() bei UDP) + man kann alle systemabh. Typen in den Protokollstructs mit typedefs aliasen und einheitlich drauf zugreifen.
-
Danke erstmal an die Antworten!
Mein Design sieht momentan noch folgendermaßen aus:
// für getaddrinfo struct net_info { public: // ... template <typename socket_type> void fill(const std::string &host, const std::string &port) {}; template <> void fill<tcp>(const std::string &host, const std::string &port) { addrinfo hints; hints.ai_socktype = SOCK_STREAM; // ... } template <> void fill<udp>(const std::string &host, const std::string &port) { addrinfo hints; hints.ai_socktype = SOCK_DGRAM; // ... } }; template <typename socket_type> class basic_socket : public network_error // für Fehlerbehandlung, fail() usw... { public: // ... private: SOCKET m_sock; net_info m_info; }; template <typename T> class socket_acceptor : public basic_socket<tcp> // dieses Erben ist ein Problem { public: // ... void bind(); void listen(); socket_stream<T> accept(); }; template <typename T> class socket_connector : public basic_socket<tcp> { public: // ... socket_stream<T> connect(); }; template <typename protocol> class socket_stream {}; template <> class socket_stream<tcp> : public network_error { public: void recv(std::vector<char> &buffer); void send(const char *msg, std::size_t length); }; template <> class socket_stream<udp> { public: void recv(std::vector<char> &buffer); void send(const char *msg, std::size_t length); }; // Später sollten auch noch sachen wie socket_stream<http> usw. möglich seinIch stehe aber noch vor folgenden Problem:
Momentan hat basic_socket die privaten Member SOCKET und net_info. Dadurch müsste ich alle Klassen die davon ableiten als friend deklarieren. Dazu benötigt socket_stream ja auch ein SOCKET, aber nicht net_info. Blöd "gelöst", irgendwelche Vorschläge?
Generell finde ich das mit dem basic_socket
blödbeschissen gelöst ... weiß aber auch gerade nicht wie ich das handeln soll.
-
Allerdings muss man u.U. Tipp 43 aus Effective C++ beachten.
Von welcher Edition redest du? Habe in Erinnerung, dass die Ordnung unterschiedlich ist.
Ansonsten: Ich finds Overkill.
-
@Jodocus
Wozubasic_socket<TCP>undbasic_socket<UDP>?
Wieso nicht einfachtcp_socketundudp_socketals zwei verschiedene Klassen?Stream-Sockets und Datagram-Sockets sind verschieden genug als dass ich nicht das selbe Template dafür verwenden würde.