
1 changed files with 129 additions and 0 deletions
@ -0,0 +1,129 @@ |
|||||
|
# Gedanken und Vorüberlegungen zu libpEpDatatype |
||||
|
|
||||
|
## Anforderungen / Designziele |
||||
|
|
||||
|
* Soll die von der pEpEngine definierten Datentypen so kapseln/wrappen, |
||||
|
dass sie in C++ bequem verwendet werden können, incl. der in C++ üblichen |
||||
|
Eigenschaften (RAII, exception safety, Implementierung der in der stdlib |
||||
|
üblichen Operatoren und (Member-)Funktionen etc.). |
||||
|
|
||||
|
* Die Wrapper sollen zusätzliche semantische Constraints der Daten sicherstellen |
||||
|
bzw. schaffen, z.B. dass alle Strings "NFC-normalisierte UTF-8"-Strings sind. |
||||
|
Ggf. sind die Daten dafür umzukopieren. |
||||
|
|
||||
|
* Die Datentypen sollen im Zusammenspiel mit den Funktionen der public API |
||||
|
der pEpEngine benutzbar sein (entweder _direkt_, z.B. über geeignete |
||||
|
Konvertierungs-Operatoren oder über einfache, verständliche Access-Funktionen). |
||||
|
|
||||
|
* Die Wrapper sollen effizient sein, in Bezug auf Speicherverbrauch und Laufzeitverhalten. |
||||
|
|
||||
|
* libpEpDatatypes soll leicht erweiterbar sein für zukünftige Datentypen. |
||||
|
|
||||
|
|
||||
|
## Implementierungs-Varianten / -Alternativen |
||||
|
|
||||
|
* thin wrapper vs. fat wrapper |
||||
|
* hangeschriebener Wrapper-Code vs. Template-Magic vs. externer Codegenerierung (YML2?) |
||||
|
|
||||
|
Codebeispiele anhand von |
||||
|
* `pEp_identity` – als einfacher Struct Typ |
||||
|
* `identity_list` – als typischer Vertreter der in der Engine zahlreich |
||||
|
vorhandenen (einfach) verketteten Listen. |
||||
|
|
||||
|
|
||||
|
### Thin Wrapper |
||||
|
|
||||
|
* Enthält den C-Datentyp als Data Member |
||||
|
* Erlaubt Zugriff auf die Member des C-Datentyps via `operator->()` |
||||
|
* (+) Code lässt sich leicht generieren, oder mit Unterstützung weniger Hilfsfunktionen per Template generieren |
||||
|
* (+) ist i.A. sehr effizient, erfordert aber ggf. Zusatzfunktionen, um unnötige doppelte |
||||
|
Stringkopien zu vermeiden |
||||
|
* (-) Der aufgerufene C-Code kann die gewrappten Daten – unbemerkt von libpEpDatatype – modifizieren, |
||||
|
so dass z.B. die Zusicherung nach NFC-normalisierten Strings nicht mehr gehalten werden kann. |
||||
|
|
||||
|
``` |
||||
|
class pEp::Wrapper<pEp_identity*> |
||||
|
{ |
||||
|
public: |
||||
|
// TODO: Soll "Ownership" von `*id` übernommen werden oder eine Kopie gemacht werden? |
||||
|
// TODO 2: 'explicit', um ungewollte Typumwandlungen zu vermeiden? |
||||
|
Identity(pEp_identity* id); |
||||
|
|
||||
|
// Ruft intern new_identity() mit den 4 Parametern auf. |
||||
|
// Die anderen Member muss man danach manuell setzen. :-| |
||||
|
// Problem: NFC-normalisierung muss vorher erfolgen -> ggf. String-Kopie. |
||||
|
// new_identity() kopiert die Strings dann erneut -> unnötig. |
||||
|
// ad-hoc-Lösung von Volker (2021-10-02): identity_dup_NFC(). Analog könnte man new_identity_NFC() hinzufügen. HACK? |
||||
|
// Evtl. wäre ein new_identity_move_ownership() oder so sinnvoller, da generischer? |
||||
|
Identity(const char* address, const char* fpr, const char* user_id, const char* username); |
||||
|
|
||||
|
// TODO: Rückgabe von wrapped_value, als "const" oder eine Kopie? |
||||
|
pEp_identity* get(); |
||||
|
|
||||
|
// alternative Syntax: Konvertierungs-Operator? |
||||
|
// TODO: automatisch oder 'explicit', um ungewollte Typumwandlungen zu vermeiden? |
||||
|
operator pEp_identity*(); |
||||
|
|
||||
|
private: |
||||
|
pEp_identity* wrapped_value; |
||||
|
}; |
||||
|
|
||||
|
using Identity = Wrapper<pEp_identity*>; |
||||
|
``` |
||||
|
|
||||
|
Beispielcode für die Verwendung: |
||||
|
|
||||
|
``` |
||||
|
pEp::Identity id("example@pep.me", "0xCAFEBABE", "example", "Elon Xaver Ample"); |
||||
|
... |
||||
|
// je nach o.g. API: |
||||
|
update_identity(session, id); |
||||
|
// oder |
||||
|
update_identity(session, id.get()); |
||||
|
``` |
||||
|
|
||||
|
PROBLEM: Die Funktion ändert die Member von 'id' in einer nicht von libpEpDataType |
||||
|
erkennbaren Weise. UTF8-NFC _kann_ danach _nicht_ mehr garantiert werden. |
||||
|
Der _derzeitige_ Code von update_identity() scheint diesbezüglich sauber zu sein. |
||||
|
|
||||
|
Aber z.B. `message->opt_fields` wird an zahlreichen Stellen modifiziert, NFC-konformität ist |
||||
|
danach nicht mehr gewährleistet. |
||||
|
|
||||
|
Mögliche Lösung: `Wrapper<T*>::get()` gibt nicht den C-Datentyp zurück, sondern ein Proxy-Objekt, |
||||
|
in dessen Destruktor alle Member-Strings auf NFC-konformität geprüft werden. |
||||
|
-> Wie zuverlässig ist das? |
||||
|
|
||||
|
|
||||
|
### Thin Wrapper für Listen |
||||
|
|
||||
|
* (+) Code kann generiert werden, auch mit C++ Template Magie |
||||
|
* (+) Sehr effizient, dank Move-Semantik |
||||
|
|
||||
|
* (-) Keine C++-Semantik bezügl. Const-Correctness |
||||
|
d.h. C-Code könnte die Member einer `const pEp::IdentityList` verändern und auch Elemente |
||||
|
hinzufügen/entfernen, was der C++-Semantik von `const` widerspricht. |
||||
|
|
||||
|
* public API der Engine reicht nicht aus, um komplette Schnittstelle von `std::forward_list<T>` |
||||
|
zu implementieren. TODO: API erweitern? Oder "hacken" und in den Interna der Datentypen direkt |
||||
|
herumfummeln? Welche Methoden, Typen und Eigenschaften von `std::forward_list<T>` werden |
||||
|
überhaupt benötigt? |
||||
|
|
||||
|
|
||||
|
### Fat Wrapper |
||||
|
|
||||
|
* Enthält eigene Memberdefinitionen. Möglich wären C-Strings oder C++-Strings. |
||||
|
* .get() generiert C-Datentyp und on-the-fly und gibt ihn zurück. Problem: Ownership? |
||||
|
* (+) Zustand von gewrappten Daten bleibt konsistent (z.B. NFC-konformität, 'const' Correctness etc.) |
||||
|
* (+) Container-API einfacher implementierbar und leistungsfähiger, da sie z.B. |
||||
|
an `std::deque<T>` statt `std::forward_list<T>` angelehnt sein könnte. |
||||
|
* (-) Laufzeit- und Speicheraufwand |
||||
|
* (--) C-Funktionen mit In-Out-Parametern sind so nicht nutzbar. TODO: Vielleicht mit Proxy-Objekten? |
||||
|
|
||||
|
### "Hybrider" Wrapper für Listen |
||||
|
* Enthält den C-Datentyp (verkettete Liste) |
||||
|
* Zusätzlich einen C++-Container (z.B. `std::deque<>`) mit Zeigern/Referenzen auf die Listenelemente |
||||
|
* (+) bietet effizienteren und komfortableren Zugriff von C++ aus als von C aus |
||||
|
* (-) Implementierungsaufwand |
||||
|
* (-) fragil und versagt, wenn C-Code an der C-Liste herumfummelt |
||||
|
|
||||
|
(to be continued) |
Loading…
Reference in new issue