C++ Generierter Code Leitfaden
Alle Unterschiede zwischen dem generierten Code für proto2, proto3 und Editionen werden hervorgehoben. Beachten Sie, dass diese Unterschiede im generierten Code liegen, wie in diesem Dokument beschrieben, und nicht in den Basisnachrichtenklassen/-schnittstellen, die in allen Versionen gleich sind. Sie sollten den proto2 Sprachleitfaden, den proto3 Sprachleitfaden oder den Edition 2023 Sprachleitfaden lesen, bevor Sie dieses Dokument lesen.
Compiler-Aufruf
Der Protocol-Buffer-Compiler erzeugt C++-Ausgabe, wenn er mit dem Befehlszeilenflag --cpp_out= aufgerufen wird. Der Parameter der Option --cpp_out= ist das Verzeichnis, in das der Compiler Ihre C++-Ausgabe schreiben soll. Der Compiler erstellt für jede .proto-Datei eine Header-Datei und eine Implementierungsdatei. Die Namen der Ausgabedateien werden berechnet, indem der Name der .proto-Datei genommen und zwei Änderungen vorgenommen werden.
- Die Erweiterung (
.proto) wird durch.pb.hbzw..pb.ccfür die Header- bzw. Implementierungsdatei ersetzt. - Der Proto-Pfad (angegeben mit dem Befehlszeilenflag
--proto_path=oder-I) wird durch den Ausgabepfad (angegeben mit dem Flag--cpp_out=) ersetzt.
Nehmen wir also zum Beispiel an, Sie rufen den Compiler wie folgt auf:
protoc --proto_path=src --cpp_out=build/gen src/foo.proto src/bar/baz.proto
Der Compiler liest die Dateien src/foo.proto und src/bar/baz.proto und erzeugt vier Ausgabedateien: build/gen/foo.pb.h, build/gen/foo.pb.cc, build/gen/bar/baz.pb.h, build/gen/bar/baz.pb.cc. Der Compiler erstellt automatisch das Verzeichnis build/gen/bar, falls erforderlich, aber er erstellt **nicht** build oder build/gen; diese müssen bereits existieren.
Pakete (Packages)
Wenn eine .proto-Datei eine package-Deklaration enthält, wird der gesamte Inhalt der Datei in einen entsprechenden C++-Namespace verschoben. Zum Beispiel, gegeben die package-Deklaration:
package foo.bar;
Alle Deklarationen in der Datei werden im Namespace foo::bar gespeichert.
Nachrichten
Angesichts einer einfachen Nachrichten Deklaration:
message Foo {}
Der Protocol-Buffer-Compiler generiert eine Klasse namens Foo, die öffentlich von google::protobuf::Message abgeleitet ist. Die Klasse ist eine konkrete Klasse; keine rein virtuellen Methoden sind nicht implementiert. Methoden, die in Message virtuell, aber nicht rein virtuell sind, können von Foo überschrieben werden oder auch nicht, abhängig vom Optimierungsmodus. Standardmäßig implementiert Foo spezialisierte Versionen aller Methoden für maximale Geschwindigkeit. Wenn jedoch die .proto-Datei die Zeile enthält:
option optimize_for = CODE_SIZE;
dann überschreibt Foo nur den minimalen Satz von Methoden, die zur Funktion notwendig sind, und verlässt sich für den Rest auf reflexionsbasierte Implementierungen. Dies reduziert die Größe des generierten Codes erheblich, verringert aber auch die Leistung. Alternativ, wenn die .proto-Datei enthält:
option optimize_for = LITE_RUNTIME;
dann enthält Foo schnelle Implementierungen aller Methoden, implementiert aber die google::protobuf::MessageLite Schnittstelle, die nur eine Teilmenge der Methoden von Message enthält. Insbesondere werden Deskriptoren oder Reflexion nicht unterstützt. In diesem Modus muss der generierte Code jedoch nur gegen libprotobuf-lite.so (libprotobuf-lite.lib unter Windows) anstelle von libprotobuf.so (libprotobuf.lib) gelinkt werden. Die „Lite“-Bibliothek ist viel kleiner als die vollständige Bibliothek und eignet sich besser für ressourcenbeschränkte Systeme wie Mobiltelefone.
Sie sollten **keine** eigenen Foo-Unterklassen erstellen. Wenn Sie diese Klasse unterklassifizieren und eine virtuelle Methode überschreiben, kann die Überschreibung ignoriert werden, da viele generierte Methodenaufrufe de-virtualisiert werden, um die Leistung zu verbessern.
Die Message-Schnittstelle definiert Methoden, mit denen Sie die gesamte Nachricht überprüfen, bearbeiten, lesen oder schreiben können, einschließlich des Parsens aus und Serialisierens in Binärstrings.
bool ParseFromString(::absl::string_view data): Parsen Sie die Nachricht aus dem gegebenen serialisierten Binärstring (auch bekannt als Wire-Format).bool SerializeToString(string* output) const: Serialisieren Sie die gegebene Nachricht in einen Binärstring.string DebugString(): Gibt eine Zeichenkette mit dertext_format-Darstellung des Proto zurück (sollte nur zum Debuggen verwendet werden).
Zusätzlich zu diesen Methoden definiert die Klasse Foo die folgenden Methoden:
Foo(): Standardkonstruktor.~Foo(): Standarddestruktor.Foo(const Foo& other): Kopierkonstruktor.Foo(Foo&& other): Move-Konstruktor.Foo& operator=(const Foo& other): Zuweisungsoperator.Foo& operator=(Foo&& other): Move-Zuweisungsoperator.void Swap(Foo* other): Inhalt mit einer anderen Nachricht tauschen.const UnknownFieldSet& unknown_fields() const: Gibt die Menge der unbekannten Felder zurück, die beim Parsen dieser Nachricht angetroffen wurden. Wennoption optimize_for = LITE_RUNTIMEin der.proto-Datei angegeben ist, ändert sich der Rückgabetyp zustd::string&.UnknownFieldSet* mutable_unknown_fields(): Gibt einen Zeiger auf die veränderliche Menge der unbekannten Felder zurück, die beim Parsen dieser Nachricht angetroffen wurden. Wennoption optimize_for = LITE_RUNTIMEin der.proto-Datei angegeben ist, ändert sich der Rückgabetyp zustd::string*.
Hinweis: Der Kopierkonstruktor und der Zuweisungsoperator führen eine tiefe Kopie der Nachrichtendaten durch. Dies stellt sicher, dass jedes Nachrichtenobjekt eine eigene Kopie der Daten besitzt und verwaltet, was Probleme wie doppelte Freigaben oder Use-after-Free-Fehler vermeidet. Dieses Verhalten entspricht der Standardpraxis in C++ für Objekte, die ihre Daten besitzen, wie z.B. std::vector. Für Entwickler, die aus Sprachen mit anderen Kopiersemantiken stammen (wie z.B. JavaScript oder TypeScript, wo flache Kopien häufiger vorkommen können), ist es wichtig zu beachten, dass Änderungen an einer kopierten Nachricht die ursprüngliche Nachricht nicht beeinträchtigen und umgekehrt.
Die Klasse definiert außerdem die folgenden statischen Methoden:
static const Descriptor* descriptor(): Gibt den Deskriptor des Typs zurück. Dieser enthält Informationen über den Typ, einschließlich seiner Felder und deren Typen. Dies kann mit Reflection verwendet werden, um Felder programmatisch zu inspizieren.static const Foo& default_instance(): Gibt eine konstante Singleton-Instanz vonFoozurück, die identisch mit einer neu erstellten Instanz vonFooist (d.h. alle singulären Felder sind nicht gesetzt und alle wiederholten Felder sind leer). Beachten Sie, dass die Standardinstanz einer Nachricht als Fabrik verwendet werden kann, indem ihreNew()-Methode aufgerufen wird.
Generierte Dateinamen
Reservierte Schlüsselwörter werden im generierten Output mit einem Unterstrich angehängt.
Zum Beispiel die folgende proto3-Definitionssyntax:
message MyMessage {
string false = 1;
string myFalse = 2;
}
generiert die folgende Teilausgabe:
void clear_false_() ;
const std::string& false_() const;
void set_false_(Arg_&& arg, Args_... args);
std::string* mutable_false_();
PROTOBUF_NODISCARD std::string* release_false_();
void set_allocated_false_(std::string* ptr);
void clear_myfalse() ;
const std::string& myfalse() const;
void set_myfalse(Arg_&& arg, Args_... args);
std::string* mutable_myfalse();
PROTOBUF_NODISCARD std::string* release_myfalse();
void set_allocated_myfalse(std::string* ptr);
Verschachtelte Typen
Eine Nachricht kann innerhalb einer anderen Nachricht deklariert werden. Zum Beispiel:
message Foo {
message Bar {}
}
In diesem Fall generiert der Compiler zwei Klassen: Foo und Foo_Bar. Zusätzlich generiert der Compiler eine Typedef innerhalb von Foo wie folgt:
typedef Foo_Bar Bar;
Das bedeutet, dass Sie die Klasse des verschachtelten Typs so verwenden können, als wäre es die verschachtelte Klasse Foo::Bar. Beachten Sie jedoch, dass C++ die Vorabdeklaration verschachtelter Typen nicht zulässt. Wenn Sie Bar in einer anderen Datei vorab deklarieren und diese Deklaration verwenden möchten, müssen Sie sie als Foo_Bar identifizieren.
Felder
Zusätzlich zu den im vorherigen Abschnitt beschriebenen Methoden generiert der Protocol-Buffer-Compiler einen Satz von Accessor-Methoden für jedes Feld, das innerhalb der Nachricht in der .proto-Datei definiert ist. Diese Methoden sind in Kleinbuchstaben/Snake-Case, z.B. has_foo() und clear_foo().
Neben den Accessor-Methoden generiert der Compiler eine Integer-Konstante für jedes Feld, die seine Feldnummer enthält. Der Konstantname ist der Buchstabe k, gefolgt vom Feldnamen in Camel-Case, gefolgt von FieldNumber. Zum Beispiel, gegeben das Feld optional int32 foo_bar = 5;, generiert der Compiler die Konstante static const int kFooBarFieldNumber = 5;.
Für Feld-Accessor, die eine const-Referenz zurückgeben, kann diese Referenz ungültig werden, wenn der nächste modifizierende Zugriff auf die Nachricht erfolgt. Dies schließt das Aufrufen eines nicht-const-Accessors eines beliebigen Feldes, das Aufrufen einer nicht-const-Methode, die von Message geerbt wurde, oder die Modifizierung der Nachricht auf andere Weise ein (z.B. durch die Verwendung der Nachricht als Argument von Swap()). Entsprechend ist die Adresse der zurückgegebenen Referenz nur garantiert über verschiedene Aufrufe des Accessors gleich, wenn in der Zwischenzeit kein modifizierender Zugriff auf die Nachricht erfolgt ist.
Für Feld-Accessor, die einen Zeiger zurückgeben, kann dieser Zeiger ungültig werden, wenn der nächste modifizierende oder nicht-modifizierende Zugriff auf die Nachricht erfolgt. Dies schließt, unabhängig von der constness, das Aufrufen eines Accessors eines beliebigen Feldes, das Aufrufen einer Methode, die von Message geerbt wurde, oder der Zugriff auf die Nachricht auf andere Weise ein (z.B. durch Kopieren der Nachricht mit dem Kopierkonstruktor). Entsprechend ist der Wert des zurückgegebenen Zeigers niemals garantiert über zwei verschiedene Aufrufe des Accessors gleich.
Numerische Felder mit expliziter Präsenz
Für Felddefinitionen für numerische Felder mit expliziter Präsenz:
int32 foo = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_foo() const: Gibttruezurück, wenn das Feld gesetzt ist.int32_t foo() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird der Standardwert zurückgegeben.void set_foo(::int32_t value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibthas_foo()truezurück undfoo()gibtvaluezurück.void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthas_foo()falsezurück undfoo()gibt den Standardwert zurück.
Für andere numerische Feldtypen (einschließlich bool) wird int32_t durch den entsprechenden C++-Typ gemäß der Tabelle der Skalarwerttypen ersetzt.
Numerische Felder mit impliziter Präsenz
Für Felddefinitionen für numerische Felder mit impliziter Präsenz:
int32 foo = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
::int32_t foo() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, gibt es 0 zurück.void set_foo(::int32_t value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibtfoo()valuezurück.void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibtfoo()0 zurück.
Für andere numerische Feldtypen (einschließlich bool) wird int32_t durch den entsprechenden C++-Typ gemäß der Tabelle der Skalarwerttypen ersetzt.
Zeichenketten-/Byte-Felder mit expliziter Präsenz
Hinweis: Ab Edition 2023, wenn features.(pb.cpp).string_type auf VIEW gesetzt ist, werden stattdessen string_view-APIs generiert.
Für diese Felddefinitionen mit expliziter Präsenz:
string foo = 1;
bytes foo = 2;
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_foo() const: Gibttruezurück, wenn das Feld gesetzt ist.const string& foo() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird der Standardwert zurückgegeben.void set_foo(...): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibthas_foo()truezurück undfoo()gibt eine Kopie vonvaluezurück.string* mutable_foo(): Gibt einen Zeiger auf das veränderlichestring-Objekt zurück, das den Wert des Feldes speichert. Wenn das Feld vor dem Aufruf nicht gesetzt war, ist der zurückgegebene String leer (nicht der Standardwert). Nach dem Aufruf dieser Methode gibthas_foo()truezurück undfoo()gibt den Wert zurück, der in den gegebenen String geschrieben wurde.Hinweis: Diese Methode wird in den neuen
string_view-APIs entfernt.void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthas_foo()falsezurück undfoo()gibt den Standardwert zurück.void set_allocated_foo(string* value): Weist dasstring-Objekt dem Feld zu und gibt den vorherigen Feldwert frei, falls vorhanden. Wenn derstring-Zeiger nichtNULList, übernimmt die Nachricht den Besitz des zugewiesenenstring-Objekts undhas_foo()gibttruezurück. Die Nachricht kann das zugewiesenestring-Objekt jederzeit löschen, daher können Referenzen auf das Objekt ungültig werden. Wenn dervalueNULList, ist das Verhalten dasselbe wie beim Aufrufen vonclear_foo().string* release_foo(): Gibt den Besitz des Feldes frei und gibt den Zeiger desstring-Objekts zurück. Nach dem Aufruf dieser Methode übernimmt der Aufrufer den Besitz des zugewiesenenstring-Objekts,has_foo()gibtfalsezurück undfoo()gibt den Standardwert zurück.
Zeichenketten-/Byte-Felder mit impliziter Präsenz
Hinweis: Ab Edition 2023, wenn features.(pb.cpp).string_type auf VIEW gesetzt ist, werden stattdessen string_view-APIs generiert.
Für diese Felddefinitionen mit impliziter Präsenz:
string foo = 1 [features.field_presence = IMPLICIT];
bytes foo = 1 [features.field_presence = IMPLICIT];
Der Compiler generiert die folgenden Accessor-Methoden:
const string& foo() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird der leere String/die leeren Bytes zurückgegeben.void set_foo(Arg_&& arg, Args_... args): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibtfoo()eine Kopie vonvaluezurück.string* mutable_foo(): Gibt einen Zeiger auf das veränderlichestring-Objekt zurück, das den Wert des Feldes speichert. Wenn das Feld vor dem Aufruf nicht gesetzt war, ist der zurückgegebene String leer. Nach dem Aufruf dieser Methode gibtfoo()den Wert zurück, der in den gegebenen String geschrieben wurde.void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibtfoo()den leeren String/die leeren Bytes zurück.void set_allocated_foo(string* value): Weist dasstring-Objekt dem Feld zu und gibt den vorherigen Feldwert frei, falls vorhanden. Wenn derstring-Zeiger nichtNULList, übernimmt die Nachricht den Besitz des zugewiesenenstring-Objekts. Die Nachricht kann das zugewiesenestring-Objekt jederzeit löschen, daher können Referenzen auf das Objekt ungültig werden. Wenn dervalueNULList, ist das Verhalten dasselbe wie beim Aufrufen vonclear_foo().string* release_foo(): Gibt den Besitz des Feldes frei und gibt den Zeiger desstring-Objekts zurück. Nach dem Aufruf dieser Methode übernimmt der Aufrufer den Besitz des zugewiesenenstring-Objekts undfoo()gibt den leeren String/die leeren Bytes zurück.
Singuläre Byte-Felder mit Cord-Unterstützung
v23.0 fügte Unterstützung für absl::Cord für singuläre bytes-Felder hinzu (einschließlich oneof-Felder). Singuläre string-, repeated string- und repeated bytes-Felder unterstützen die Verwendung von Cords nicht.
Um ein singuläres bytes-Feld so einzustellen, dass Daten mit absl::Cord gespeichert werden, verwenden Sie die folgende Syntax:
// edition (default settings)
bytes foo = 25 [ctype=CORD];
bytes foo = 26 [ctype=CORD, features.field_presence = IMPLICIT];
Die Verwendung von cord ist für repeated bytes-Felder nicht verfügbar. Protoc ignoriert [ctype=CORD]-Einstellungen bei diesen Feldern.
Der Compiler generiert die folgenden Accessor-Methoden:
const ::absl::Cord& foo() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird ein leererCord(proto3) oder der Standardwert (proto2 und Editionen) zurückgegeben.void set_foo(const ::absl::Cord& value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibtfoo()valuezurück.void set_foo(::absl::string_view value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibtfoo()valuealsabsl::Cordzurück.void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibtfoo()einen leerenCord(proto3) oder den Standardwert (proto2 und Editionen) zurück.bool has_foo(): Gibttruezurück, wenn das Feld gesetzt ist. Gilt nur für das Feldoptionalin proto3 und für das Feld mit expliziter Präsenz in Editionen.
Enum-Felder mit expliziter Präsenz
Gegeben den Enum-Typ:
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
Für diese Felddefinition mit expliziter Präsenz:
Bar bar = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_bar() const: Gibttruezurück, wenn das Feld gesetzt ist.Bar bar() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird der Standardwert zurückgegeben.void set_bar(Bar value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibthas_bar()truezurück undbar()gibtvaluezurück. Im Debug-Modus (d.h. NDEBUG ist nicht definiert) wird der Prozess abgebrochen, wennvaluekeinem der fürBardefinierten Werte entspricht.void clear_bar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthas_bar()falsezurück undbar()gibt den Standardwert zurück.
Enum-Felder mit impliziter Präsenz
Gegeben den Enum-Typ:
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
Für diese Felddefinition mit impliziter Präsenz:
Bar bar = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
Bar bar() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, gibt es den Standardwert (0) zurück.void set_bar(Bar value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibtbar()valuezurück.void clear_bar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibtbar()den Standardwert zurück.
Eingebettete Nachrichtenfelder mit expliziter Präsenz
Gegeben den Nachrichtentyp:
message Bar {}
Für diese Felddefinition mit expliziter Präsenz:
Bar bar = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_bar() const: Gibttruezurück, wenn das Feld gesetzt ist.const Bar& bar() const: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird eineBarzurückgegeben, bei der keine Felder gesetzt sind (möglicherweiseBar::default_instance()).Bar* mutable_bar(): Gibt einen Zeiger auf das veränderlicheBar-Objekt zurück, das den Wert des Feldes speichert. Wenn das Feld vor dem Aufruf nicht gesetzt war, dann hat die zurückgegebeneBarkeine gesetzten Felder (d.h. sie ist identisch mit einer neu zugewiesenenBar). Nach dem Aufruf dieser Methode gibthas_bar()truezurück undbar()gibt eine Referenz auf dieselbe Instanz vonBarzurück.void clear_bar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthas_bar()falsezurück undbar()gibt den Standardwert zurück.void set_allocated_bar(Bar* value): Weist dasBar-Objekt dem Feld zu und gibt den vorherigen Feldwert frei, falls vorhanden. Wenn derBar-Zeiger nichtNULList, übernimmt die Nachricht den Besitz des zugewiesenenBar-Objekts undhas_bar()gibttruezurück. Wenn derBarNULList, ist das Verhalten dasselbe wie beim Aufrufen vonclear_bar().Bar* release_bar(): Gibt den Besitz des Feldes frei und gibt den Zeiger desBar-Objekts zurück. Nach dem Aufruf dieser Methode übernimmt der Aufrufer den Besitz des zugewiesenenBar-Objekts,has_bar()gibtfalsezurück undbar()gibt den Standardwert zurück.
Wiederholte numerische Felder
Für diese Felddefinition:
repeated int32 foo = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
int foo_size() const: Gibt die Anzahl der Elemente zurück, die sich derzeit im Feld befinden. Um ein leeres Set zu prüfen, sollten Sie dieempty()-Methode im zugrunde liegendenRepeatedFieldanstelle dieser Methode verwenden.int32_t foo(int index) const: Gibt das Element am gegebenen nullbasierten Index zurück. Das Aufrufen dieser Methode mit einem Index außerhalb von [0, foo_size()) führt zu undefiniertem Verhalten.void set_foo(int index, int32_t value): Setzt den Wert des Elements am gegebenen nullbasierten Index.void add_foo(int32_t value): Fügt ein neues Element am Ende des Feldes mit dem gegebenen Wert an.void clear_foo(): Entfernt alle Elemente aus dem Feld. Nach dem Aufruf dieser Methode gibtfoo_size()Null zurück.const RepeatedField<int32_t>& foo() const: Gibt das zugrunde liegendeRepeatedFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.RepeatedField<int32_t>* mutable_foo(): Gibt einen Zeiger auf das zugrunde liegende veränderlicheRepeatedFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.
Für andere numerische Feldtypen (einschließlich bool) wird int32_t durch den entsprechenden C++-Typ gemäß der Tabelle der Skalarwerttypen ersetzt.
Wiederholte Zeichenkettenfelder
Hinweis: Ab Edition 2023, wenn features.(pb.cpp).string_type auf VIEW gesetzt ist, werden stattdessen string_view-APIs generiert.
Für eine dieser Felddefinitionen:
repeated string foo = 1;
repeated bytes foo = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
int foo_size() const: Gibt die Anzahl der Elemente zurück, die sich derzeit im Feld befinden. Um ein leeres Set zu prüfen, sollten Sie dieempty()-Methode im zugrunde liegendenRepeatedFieldanstelle dieser Methode verwenden.const string& foo(int index) const: Gibt das Element am gegebenen nullbasierten Index zurück. Das Aufrufen dieser Methode mit einem Index außerhalb von [0, foo_size()-1] führt zu undefiniertem Verhalten.void set_foo(int index, ::absl::string_view value): Setzt den Wert des Elements am gegebenen nullbasierten Index.void set_foo(int index, const string& value): Setzt den Wert des Elements am gegebenen nullbasierten Index.void set_foo(int index, string&& value): Setzt den Wert des Elements am gegebenen nullbasierten Index und verschiebt den übergebenen String.void set_foo(int index, const char* value): Setzt den Wert des Elements am gegebenen nullbasierten Index unter Verwendung eines C-Style-Strings mit Nullterminierung.void set_foo(int index, const char* value, int size): Setzt den Wert des Elements am gegebenen nullbasierten Index unter Verwendung eines C-Style-Strings mit explizit angegebener Größe anstelle der durch Suche nach dem Nullterminierungsbyte bestimmten Größe.string* mutable_foo(int index): Gibt einen Zeiger auf das veränderlichestring-Objekt zurück, das den Wert des Elements am gegebenen nullbasierten Index speichert. Das Aufrufen dieser Methode mit einem Index außerhalb von [0, foo_size()) führt zu undefiniertem Verhalten.void add_foo(::absl::string_view value): Fügt ein neues Element am Ende des Feldes mit dem gegebenen Wert an.void add_foo(const string& value): Fügt ein neues Element am Ende des Feldes mit dem gegebenen Wert an.void add_foo(string&& value): Fügt ein neues Element am Ende des Feldes an und verschiebt den übergebenen String.void add_foo(const char* value): Fügt ein neues Element am Ende des Feldes unter Verwendung eines C-Style-Strings mit Nullterminierung an.void add_foo(const char* value, int size): Fügt ein neues Element am Ende des Feldes unter Verwendung eines Strings mit explizit angegebener Größe anstelle der durch Suche nach dem Nullterminierungsbyte bestimmten Größe an.string* add_foo(): Fügt ein neues leeres String-Element am Ende des Feldes hinzu und gibt einen Zeiger darauf zurück.void clear_foo(): Entfernt alle Elemente aus dem Feld. Nach dem Aufruf dieser Methode gibtfoo_size()Null zurück.const RepeatedPtrField<string>& foo() const: Gibt das zugrunde liegendeRepeatedPtrFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.RepeatedPtrField<string>* mutable_foo(): Gibt einen Zeiger auf das zugrunde liegende veränderlicheRepeatedPtrFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.
Wiederholte Enum-Felder
Gegeben den Enum-Typ:
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
Für diese Felddefinition:
repeated Bar bar = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
int bar_size() const: Gibt die Anzahl der Elemente zurück, die sich derzeit im Feld befinden. Um ein leeres Set zu prüfen, sollten Sie dieempty()-Methode im zugrunde liegendenRepeatedFieldanstelle dieser Methode verwenden.Bar bar(int index) const: Gibt das Element am gegebenen nullbasierten Index zurück. Das Aufrufen dieser Methode mit einem Index außerhalb von [0, bar_size()) führt zu undefiniertem Verhalten.void set_bar(int index, Bar value): Setzt den Wert des Elements am gegebenen nullbasierten Index. Im Debug-Modus (d.h. NDEBUG ist nicht definiert) wird der Prozess abgebrochen, wennvaluekeinem der fürBardefinierten Werte entspricht und es sich um ein geschlossenes Enum handelt.void add_bar(Bar value): Fügt ein neues Element am Ende des Feldes mit dem gegebenen Wert an. Im Debug-Modus (d.h. NDEBUG ist nicht definiert) wird der Prozess abgebrochen, wennvaluekeinem der fürBardefinierten Werte entspricht.void clear_bar(): Entfernt alle Elemente aus dem Feld. Nach dem Aufruf dieser Methode gibtbar_size()Null zurück.const RepeatedField<int>& bar() const: Gibt das zugrunde liegendeRepeatedFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.RepeatedField<int>* mutable_bar(): Gibt einen Zeiger auf das zugrunde liegende veränderlicheRepeatedFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.
Wiederholte eingebettete Nachrichtenfelder
Gegeben den Nachrichtentyp:
message Bar {}
Für diese Felddefinitionen:
repeated Bar bar = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
int bar_size() const: Gibt die Anzahl der Elemente zurück, die sich derzeit im Feld befinden. Um ein leeres Set zu prüfen, sollten Sie dieempty()-Methode im zugrunde liegendenRepeatedFieldanstelle dieser Methode verwenden.const Bar& bar(int index) const: Gibt das Element am gegebenen nullbasierten Index zurück. Das Aufrufen dieser Methode mit einem Index außerhalb von [0, bar_size()) führt zu undefiniertem Verhalten.Bar* mutable_bar(int index): Gibt einen Zeiger auf das veränderlicheBar-Objekt zurück, das den Wert des Elements am gegebenen nullbasierten Index speichert. Das Aufrufen dieser Methode mit einem Index außerhalb von [0, bar_size()) führt zu undefiniertem Verhalten.Bar* add_bar(): Fügt ein neues Element am Ende des Feldes hinzu und gibt einen Zeiger darauf zurück. Die zurückgegebeneBarist veränderlich und hat keine gesetzten Felder (d.h. sie ist identisch mit einer neu zugewiesenenBar).void clear_bar(): Entfernt alle Elemente aus dem Feld. Nach dem Aufruf dieser Methode gibtbar_size()Null zurück.const RepeatedPtrField<Bar>& bar() const: Gibt das zugrunde liegendeRepeatedPtrFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.RepeatedPtrField<Bar>* mutable_bar(): Gibt einen Zeiger auf das zugrunde liegende veränderlicheRepeatedPtrFieldzurück, das die Elemente des Feldes speichert. Diese Containerklasse bietet STL-ähnliche Iteratoren und weitere Methoden.
Oneof-Numerikfelder
Für diese oneof-Felddefinition:
oneof example_name {
int32 foo = 1;
...
}
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_foo() const: Gibttruezurück, wenn der Oneof-FallkFooist.int32 foo() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-FallkFooist. Andernfalls wird der Standardwert zurückgegeben.void set_foo(int32 value):- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
clear_example_name()aufgerufen. - Setzt den Wert dieses Feldes und setzt den Oneof-Fall auf
kFoo. has_foo()gibt danntruezurück,foo()gibtvaluezurück undexample_name_case()gibtkFoozurück.
- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
void clear_foo():- Wenn der Oneof-Fall nicht
kFooist, wird nichts geändert. - Wenn der Oneof-Fall
kFooist, wird der Wert des Feldes und der Oneof-Fall gelöscht.has_foo()gibt dannfalsezurück,foo()gibt den Standardwert zurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück.
- Wenn der Oneof-Fall nicht
Für andere numerische Feldtypen (einschließlich bool) wird int32_t durch den entsprechenden C++-Typ gemäß der Tabelle der Skalarwerttypen ersetzt.
Oneof-Zeichenkettenfelder
Hinweis: Ab Edition 2023 können string_view-APIs generiert werden.
Für eine dieser oneof-Felddefinitionen:
oneof example_name {
string foo = 1;
...
}
oneof example_name {
bytes foo = 1;
...
}
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_foo() const: Gibttruezurück, wenn der Oneof-FallkFooist.const string& foo() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-FallkFooist. Andernfalls wird der Standardwert zurückgegeben.void set_foo(::absl::string_view value):- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
clear_example_name()aufgerufen. - Setzt den Wert dieses Feldes und setzt den Oneof-Fall auf
kFoo. has_foo()gibt danntruezurück,foo()gibt eine Kopie vonvaluezurück undexample_name_case()gibtkFoozurück.
- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
void set_foo(const string& value): Ähnlich wie das ersteset_foo(), kopiert aber von einer const String-Referenz.void set_foo(string&& value): Ähnlich wie das ersteset_foo(), verschiebt aber den übergebenen String.void set_foo(const char* value): Ähnlich wie das ersteset_foo(), kopiert aber von einem C-Style-String mit Nullterminierung.void set_foo(const char* value, int size): Ähnlich wie das ersteset_foo(), kopiert aber von einem String mit explizit angegebener Größe anstelle der durch Suche nach dem Nullterminierungsbyte bestimmten Größe.string* mutable_foo():- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
clear_example_name()aufgerufen. - Setzt den Oneof-Fall auf
kFoound gibt einen Zeiger auf das veränderliche String-Objekt zurück, das den Wert des Feldes speichert. Wenn der Oneof-Fall vor dem Aufruf nichtkFoowar, ist der zurückgegebene String leer (nicht der Standardwert). has_foo()gibt danntruezurück,foo()gibt den Wert zurück, der in den gegebenen String geschrieben wurde, undexample_name_case()gibtkFoozurück.
- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
void clear_foo():- Wenn der Oneof-Fall nicht
kFooist, werden keine Änderungen vorgenommen. - Wenn der Oneof-Fall
kFooist, wird das Feld freigegeben und der Oneof-Fall gelöscht.has_foo()gibt dannfalsezurück,foo()gibt den Standardwert zurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück.
- Wenn der Oneof-Fall nicht
void set_allocated_foo(string* value):- Ruft
clear_example_name()auf. - Wenn der String-Zeiger nicht
NULList: Weist das String-Objekt dem Feld zu und setzt den Oneof-Fall aufkFoo. Die Nachricht übernimmt den Besitz des zugewiesenen String-Objekts,has_foo()gibttruezurück undexample_name_case()gibtkFoozurück. - Wenn der String-Zeiger
NULList, gibthas_foo()falsezurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück.
- Ruft
string* release_foo():- Gibt
NULLzurück, wenn der Oneof-Fall nichtkFooist. - Löscht den Oneof-Fall, gibt den Besitz des Feldes frei und gibt den Zeiger des String-Objekts zurück. Nach dem Aufruf dieser Methode übernimmt der Aufrufer den Besitz des zugewiesenen String-Objekts,
has_foo()gibtfalsezurück,foo()gibt den Standardwert zurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück.
- Gibt
Oneof-Enum-Felder
Gegeben den Enum-Typ:
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
Für die oneof-Felddefinition:
oneof example_name {
Bar bar = 1;
...
}
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_bar() const: Gibttruezurück, wenn der Oneof-FallkBarist.Bar bar() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-FallkBarist. Andernfalls wird der Standardwert zurückgegeben.void set_bar(Bar value):- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
clear_example_name()aufgerufen. - Setzt den Wert dieses Feldes und setzt den Oneof-Fall auf
kBar. has_bar()gibttruezurück,bar()gibtvaluezurück undexample_name_case()gibtkBarzurück.- Im Debug-Modus (d. h. NDEBUG ist nicht definiert), wenn
valuenicht mit einem der fürBardefinierten Werte übereinstimmt und es sich um ein geschlossenes Enum handelt, wird der Prozess abgebrochen.
- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
void clear_bar():- Es wird nichts geändert, wenn der Oneof-Fall nicht
kBarist. - Wenn der Oneof-Fall
kBarist, wird der Wert des Feldes und der Oneof-Fall gelöscht.has_bar()gibtfalsezurück,bar()gibt den Standardwert zurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück.
- Es wird nichts geändert, wenn der Oneof-Fall nicht
Oneof-Felder für eingebettete Nachrichten
Gegeben den Nachrichtentyp:
message Bar {}
Für die oneof-Felddefinition:
oneof example_name {
Bar bar = 1;
...
}
Der Compiler generiert die folgenden Accessor-Methoden:
bool has_bar() const: Gibt true zurück, wenn der Oneof-FallkBarist.const Bar& bar() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-FallkBarist. Andernfalls wird eine Bar zurückgegeben, bei der keine Felder gesetzt sind (möglicherweiseBar::default_instance()).Bar* mutable_bar():- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
clear_example_name()aufgerufen. - Setzt den Oneof-Fall auf
kBarund gibt einen Zeiger auf das modifizierbare Bar-Objekt zurück, das den Feldwert speichert. Wenn der Oneof-Fall vor dem Aufruf nichtkBarwar, dann hat die zurückgegebene Bar keine gesetzten Felder (d. h. sie ist identisch mit einer neu zugewiesenen Bar). - Nach diesem Aufruf gibt
has_bar()truezurück,bar()gibt eine Referenz auf dieselbe Instanz vonBarzurück undexample_name_case()gibtkBarzurück.
- Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird
void clear_bar():- Es wird nichts geändert, wenn der Oneof-Fall nicht
kBarist. - Wenn der Oneof-Fall gleich
kBarist, wird das Feld freigegeben und der Oneof-Fall gelöscht.has_bar()gibtfalsezurück,bar()gibt den Standardwert zurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück.
- Es wird nichts geändert, wenn der Oneof-Fall nicht
void set_allocated_bar(Bar* bar):- Ruft
clear_example_name()auf. - Wenn der
Bar-Zeiger nichtNULList: Setzt dasBar-Objekt auf das Feld und setzt den Oneof-Fall aufkBar. Die Nachricht übernimmt das Eigentum an dem zugewiesenenBar-Objekt,has_bar()gibt true zurück undexample_name_case()gibtkBarzurück. - Wenn der Zeiger
NULList, gibthas_bar()falsezurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück. (Das Verhalten ist wie beim Aufruf vonclear_example_name())
- Ruft
Bar* release_bar():- Gibt
NULLzurück, wenn der Oneof-Fall nichtkBarist. - Wenn der Oneof-Fall
kBarist, wird der Oneof-Fall gelöscht, das Eigentum am Feld freigegeben und der Zeiger auf dasBar-Objekt zurückgegeben. Nach diesem Aufruf übernimmt der Aufrufer das Eigentum am zugewiesenenBar-Objekt,has_bar()gibtfalsezurück,bar()gibt den Standardwert zurück undexample_name_case()gibtEXAMPLE_NAME_NOT_SETzurück.
- Gibt
Map-Felder
Für diese Map-Felddefinition
map<int32, int32> weight = 1;
Der Compiler generiert die folgenden Accessor-Methoden:
const google::protobuf::Map<int32, int32>& weight();: Gibt eine unveränderlicheMapzurück.google::protobuf::Map<int32, int32>* mutable_weight();: Gibt eine veränderlicheMapzurück.
Eine google::protobuf::Map ist ein spezieller Containertyp, der in Protocol Buffers zur Speicherung von Map-Feldern verwendet wird. Wie aus der nachstehenden Schnittstelle hervorgeht, verwendet sie eine häufig verwendete Teilmenge der Methoden von std::map und std::unordered_map.
Hinweis
Diese Maps sind ungeordnet.template<typename Key, typename T> {
class Map {
// Member types
typedef Key key_type;
typedef T mapped_type;
typedef MapPair< Key, T > value_type;
// Iterators
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
// Capacity
int size() const;
bool empty() const;
// Element access
T& operator[](const Key& key);
const T& at(const Key& key) const;
T& at(const Key& key);
// Lookup
bool contains(const Key& key) const;
int count(const Key& key) const;
const_iterator find(const Key& key) const;
iterator find(const Key& key);
// Modifiers
pair<iterator, bool> insert(const value_type& value);
template<class InputIt>
void insert(InputIt first, InputIt last);
size_type erase(const Key& Key);
iterator erase(const_iterator pos);
iterator erase(const_iterator first, const_iterator last);
void clear();
// Copy
Map(const Map& other);
Map& operator=(const Map& other);
}
Der einfachste Weg, Daten hinzuzufügen, ist die Verwendung der normalen Map-Syntax, z. B.
std::unique_ptr<ProtoName> my_enclosing_proto(new ProtoName);
(*my_enclosing_proto->mutable_weight())[my_key] = my_value;
pair<iterator, bool> insert(const value_type& value) verursacht implizit eine tiefe Kopie der value_type-Instanz. Der effizienteste Weg, einen neuen Wert in eine google::protobuf::Map einzufügen, ist wie folgt:
T& operator[](const Key& key): map[new_key] = new_mapped;
Verwendung von google::protobuf::Map mit Standard-Maps
google::protobuf::Map unterstützt die gleiche Iterator-API wie std::map und std::unordered_map. Wenn Sie google::protobuf::Map nicht direkt verwenden möchten, können Sie eine google::protobuf::Map wie folgt in eine Standard-Map konvertieren:
std::map<int32, int32> standard_map(message.weight().begin(),
message.weight().end());
Beachten Sie, dass dies eine tiefe Kopie der gesamten Map erstellt.
Sie können auch wie folgt eine google::protobuf::Map aus einer Standard-Map erstellen:
google::protobuf::Map<int32, int32> weight(standard_map.begin(), standard_map.end());
Parsen unbekannter Werte
On the wire ist eine .proto-Map äquivalent zu einer Map-Eintragsnachricht für jedes Schlüssel/Wert-Paar, während die Map selbst ein wiederholtes Feld von Map-Einträgen ist. Wie bei gewöhnlichen Nachrichtentypen ist es möglich, dass eine geparste Map-Eintragsnachricht unbekannte Felder enthält: z. B. ein Feld vom Typ int64 in einer Map, die als map<int32, string> definiert ist.
Wenn unbekannte Felder im Wire-Format einer Map-Eintragsnachricht vorhanden sind, werden diese verworfen.
Wenn ein unbekannter Enum-Wert im Wire-Format einer Map-Eintragsnachricht vorhanden ist, wird er je nach proto2, proto3 und Editionen unterschiedlich behandelt. In proto2 wird die gesamte Map-Eintragsnachricht in den unbekannten Feldsatz der enthaltenden Nachricht aufgenommen. In proto3 wird sie in ein Map-Feld aufgenommen, als ob es ein bekannter Enum-Wert wäre. Mit Editionen spiegelt es standardmäßig das proto3-Verhalten wider. Wenn features.enum_type auf CLOSED gesetzt ist, spiegelt es das proto2-Verhalten wider.
Any
Gegeben ein Any-Feld wie dieses
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
google.protobuf.Any details = 2;
}
In unserem generierten Code gibt der Getter für das details-Feld eine Instanz von google::protobuf::Any zurück. Dies bietet die folgenden speziellen Methoden zum Packen und Entpacken der Werte von Any:
class Any {
public:
// Packs the given message into this Any using the default type URL
// prefix “type.googleapis.com”. Returns false if serializing the message failed.
bool PackFrom(const google::protobuf::Message& message);
// Packs the given message into this Any using the given type URL
// prefix. Returns false if serializing the message failed.
bool PackFrom(const google::protobuf::Message& message,
::absl::string_view type_url_prefix);
// Unpacks this Any to a Message. Returns false if this Any
// represents a different protobuf type or parsing fails.
bool UnpackTo(google::protobuf::Message* message) const;
// Returns true if this Any represents the given protobuf type.
template<typename T> bool Is() const;
}
Oneof
Gegeben eine Oneof-Definition wie diese
oneof example_name {
int32 foo_int = 4;
string foo_string = 9;
...
}
Der Compiler generiert den folgenden C++-Enum-Typ
enum ExampleNameCase {
kFooInt = 4,
kFooString = 9,
EXAMPLE_NAME_NOT_SET = 0
}
Zusätzlich generiert er diese Methoden
ExampleNameCase example_name_case() const: Gibt das Enum zurück, das angibt, welches Feld gesetzt ist. GibtEXAMPLE_NAME_NOT_SETzurück, wenn keines davon gesetzt ist.void clear_example_name(): Gibt das Objekt frei, wenn das Oneof-Feld einen Zeiger verwendet (Message oder String), und setzt den Oneof-Fall aufEXAMPLE_NAME_NOT_SET.
Aufzählungen (Enums)
Hinweis: Ab der Edition 2024 können string_view-APIs mit bestimmten Feature-Einstellungen generiert werden. Weitere Informationen hierzu finden Sie unter Enumeration Name Helper.
Gegeben eine Enum-Definition wie
enum Foo {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
Der Protocol Buffer Compiler generiert einen C++-Enum-Typ namens Foo mit demselben Wertesatz. Darüber hinaus generiert der Compiler die folgenden Funktionen:
const EnumDescriptor* Foo_descriptor(): Gibt den Deskriptor des Typs zurück, der Informationen darüber enthält, welche Werte dieser Enum-Typ definiert.bool Foo_IsValid(int value): Gibttruezurück, wenn der angegebene numerische Wert mit einem der definierten Werte vonFooübereinstimmt. Im obigen Beispiel wäre diestrue, wenn die Eingabe 0, 5 oder 1234 wäre.const string& Foo_Name(int value): Gibt den Namen für den angegebenen numerischen Wert zurück. Gibt eine leere Zeichenkette zurück, wenn kein solcher Wert existiert. Wenn mehrere Werte diese Zahl haben, wird der zuerst definierte zurückgegeben. Im obigen Beispiel würdeFoo_Name(5)"VALUE_B"zurückgeben.bool Foo_Parse(::absl::string_view name, Foo* value): Wennnameein gültiger Wertname für dieses Enum ist, wird dieser Wert invaluezugewiesen und true zurückgegeben. Andernfalls wird false zurückgegeben. Im obigen Beispiel würdeFoo_Parse("VALUE_C", &some_foo)true zurückgeben undsome_fooauf 1234 setzen.const Foo Foo_MIN: der kleinste gültige Wert des Enums (VALUE_A im Beispiel).const Foo Foo_MAX: der größte gültige Wert des Enums (VALUE_C im Beispiel).const int Foo_ARRAYSIZE: immer definiert alsFoo_MAX + 1.
Seien Sie vorsichtig beim Umwandeln von Ganzzahlen in proto2-Enums. Wenn eine Ganzzahl in einen proto2-Enum-Wert umgewandelt wird, *muss* die Ganzzahl einer der gültigen Werte für dieses Enum sein, andernfalls können die Ergebnisse undefiniert sein. Im Zweifelsfall verwenden Sie die generierte Funktion Foo_IsValid(), um zu testen, ob die Umwandlung gültig ist. Das Setzen eines Enum-Typs eines proto2-Nachricht auf einen ungültigen Wert kann zu einem Assertionsfehler führen. Wenn ein ungültiger Enum-Wert beim Parsen einer proto2-Nachricht gelesen wird, wird er als unbekannter Feld behandelt. Diese Semantik wurde in proto3 geändert. Es ist sicher, jede Ganzzahl in einen proto3-Enum-Wert umzuwandeln, solange sie in int32 passt. Ungültige Enum-Werte werden auch beim Parsen einer proto3-Nachricht beibehalten und von Enum-Feld-Accessoren zurückgegeben.
Seien Sie vorsichtig bei der Verwendung von proto3- und Editionen-Enums in switch-Anweisungen. Proto3- und Editionen-Enums sind offene Enum-Typen mit möglichen Werten außerhalb des Bereichs der angegebenen Symbole. (Editionen-Enums können mit dem Feature enum_type auf geschlossene Enums gesetzt werden.) Nicht erkannte Enum-Werte für offene Enum-Typen werden beim Parsen einer Nachricht beibehalten und von den Enum-Feld-Accessoren zurückgegeben. Eine switch-Anweisung für ein offenes Enum ohne default-Fall kann nicht alle Fälle abdecken, auch wenn alle bekannten Felder aufgeführt sind. Dies kann zu unerwartetem Verhalten führen, einschließlich Datenbeschädigung und Laufzeitabstürzen. Fügen Sie immer einen default-Fall hinzu oder rufen Sie explizit Foo_IsValid(int) außerhalb der switch auf, um unbekannte Enum-Werte zu behandeln.
Sie können ein Enum innerhalb eines Nachrichtentyps definieren. In diesem Fall generiert der Protocol Buffer Compiler Code, der es so erscheinen lässt, als wäre der Enum-Typ selbst innerhalb der Klasse der Nachricht verschachtelt deklariert worden. Die Funktionen Foo_descriptor() und Foo_IsValid() werden als statische Methoden deklariert. Tatsächlich sind der Enum-Typ selbst und seine Werte im globalen Geltungsbereich mit mangled Namen deklariert und werden mit einem typedef und einer Reihe von Konstanten-Definitionen in den Geltungsbereich der Klasse importiert. Dies geschieht nur, um Probleme mit der Deklarationsreihenfolge zu umgehen. Verlassen Sie sich nicht auf die gemangelten Namen auf oberster Ebene; tun Sie so, als wäre das Enum wirklich in der Nachrichtenkasse verschachtelt.
Erweiterungen (nur proto2 und Editionen)
Gegeben eine Nachricht mit einem Erweiterungsbereich
message Foo {
extensions 100 to 199;
}
Der Protocol Buffer Compiler generiert zusätzliche Methoden für Foo: HasExtension(), ExtensionSize(), ClearExtension(), GetExtension(), SetExtension(), MutableExtension(), AddExtension(), SetAllocatedExtension() und ReleaseExtension(). Jede dieser Methoden nimmt als ersten Parameter einen Erweiterungsidentifikator (der später in diesem Abschnitt beschrieben wird), der ein Erweiterungsfeld identifiziert. Die restlichen Parameter und der Rückgabewert sind genau dieselben wie bei den entsprechenden Zugriffsermethoden, die für ein normales (nicht erweiterndes) Feld desselben Typs wie der Erweiterungsidentifikator generiert würden. (GetExtension() entspricht den Accessoren ohne spezielles Präfix.)
Gegeben eine Erweiterungsdefinition
extend Foo {
optional int32 bar = 123;
repeated int32 repeated_bar = 124;
optional Bar message_bar = 125;
}
Für das singuläre Erweiterungsfeld bar generiert der Protocol Buffer Compiler einen "Erweiterungsidentifikator" namens bar, den Sie mit den Erweiterungsaccessoren von Foo verwenden können, um auf diese Erweiterung zuzugreifen, wie folgt:
Foo foo;
assert(!foo.HasExtension(bar));
foo.SetExtension(bar, 1);
assert(foo.HasExtension(bar));
assert(foo.GetExtension(bar) == 1);
foo.ClearExtension(bar);
assert(!foo.HasExtension(bar));
Für das Nachrichten-Erweiterungsfeld message_bar gibt foo.GetExtension(message_bar), wenn das Feld nicht gesetzt ist, eine Bar zurück, bei der keine Felder gesetzt sind (möglicherweise Bar::default_instance()).
Ebenso generiert der Compiler für das wiederholte Erweiterungsfeld repeated_bar einen Erweiterungsidentifikator namens repeated_bar, den Sie ebenfalls mit den Erweiterungsaccessoren von Foo verwenden können:
Foo foo;
for (int i = 0; i < kSize; ++i) {
foo.AddExtension(repeated_bar, i)
}
assert(foo.ExtensionSize(repeated_bar) == kSize)
for (int i = 0; i < kSize; ++i) {
assert(foo.GetExtension(repeated_bar, i) == i)
}
(Die genaue Implementierung von Erweiterungsidentifikatoren ist kompliziert und beinhaltet magischen Gebrauch von Templates - Sie müssen sich jedoch nicht darum kümmern, wie Erweiterungsidentifikatoren funktionieren, um sie zu verwenden.)
Erweiterungen können innerhalb eines anderen Typs verschachtelt deklariert werden. Ein gängiges Muster ist beispielsweise folgendes:
message Baz {
extend Foo {
optional Baz foo_ext = 124;
}
}
In diesem Fall ist der Erweiterungsidentifikator foo_ext innerhalb von Baz verschachtelt deklariert. Er kann wie folgt verwendet werden:
Foo foo;
Baz* baz = foo.MutableExtension(Baz::foo_ext);
FillInMyBaz(baz);
Arena-Allokation
Arena-Allokation ist eine reine C++-Funktion, die Ihnen hilft, Ihren Speicherverbrauch zu optimieren und die Leistung bei der Arbeit mit Protokollpuffern zu verbessern. Das Aktivieren der Arena-Allokation in Ihrer .proto-Datei fügt Ihren generierten C++-Code zusätzlichen Code für die Arbeit mit Arenen hinzu. Weitere Informationen zur Arena-Allokations-API finden Sie im Arena Allocation Guide.
Services
Wenn die .proto-Datei die folgende Zeile enthält:
option cc_generic_services = true;
dann generiert der Protocol Buffer Compiler Code basierend auf den in der Datei gefundenen Service-Definitionen, wie in diesem Abschnitt beschrieben. Der generierte Code ist jedoch möglicherweise unerwünscht, da er nicht an ein bestimmtes RPC-System gebunden ist und somit mehr Indirektionsstufen erfordert als code, der für ein bestimmtes System maßgeschneidert ist. Wenn Sie NICHT möchten, dass dieser Code generiert wird, fügen Sie diese Zeile zur Datei hinzu:
option cc_generic_services = false;
Wenn keine der oben genannten Zeilen angegeben ist, ist die Option standardmäßig false, da generische Dienste veraltet sind. (Beachten Sie, dass vor 2.4.0 die Option standardmäßig true ist)
RPC-Systeme, die auf .proto-Sprachservice-Definitionen basieren, sollten Plugins bereitstellen, um Code zu generieren, der für das System geeignet ist. Diese Plugins erfordern wahrscheinlich, dass abstrakte Dienste deaktiviert werden, damit sie eigene Klassen mit denselben Namen generieren können.
Der Rest dieses Abschnitts beschreibt, was der Protocol Buffer Compiler generiert, wenn abstrakte Dienste aktiviert sind.
Schnittstelle
Gegeben eine Service-Definition
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
Der Protocol Buffer Compiler generiert eine Klasse Foo, um diesen Dienst darzustellen. Foo hat eine virtuelle Methode für jede in der Service-Definition definierte Methode. In diesem Fall ist die Methode Bar wie folgt definiert:
virtual void Bar(RpcController* controller, const FooRequest* request,
FooResponse* response, Closure* done);
Die Parameter sind äquivalent zu den Parametern von Service::CallMethod(), mit dem Unterschied, dass das Argument method impliziert ist und request und response ihren genauen Typ angeben.
Diese generierten Methoden sind virtuell, aber nicht rein virtuell. Die Standardimplementierungen rufen einfach controller->SetFailed() mit einer Fehlermeldung auf, die angibt, dass die Methode nicht implementiert ist, und rufen dann den done-Callback auf. Bei der Implementierung Ihres eigenen Dienstes müssen Sie diesen generierten Dienst unterklassifizieren und seine Methoden entsprechend implementieren.
Foo unterklassifiziert die Service-Schnittstelle. Der Protocol Buffer Compiler generiert automatisch Implementierungen der Methoden von Service wie folgt:
GetDescriptor: Gibt denServiceDescriptordes Dienstes zurück.CallMethod: Ermittelt anhand des bereitgestellten Methoden-Deskriptors, welche Methode aufgerufen wird, und ruft sie direkt auf, wobei die Anfrage- und Antwortobjekte auf die richtigen Typen heruntergestuft werden.GetRequestPrototypeundGetResponsePrototype: Geben die Standardinstanz der Anfrage oder Antwort des richtigen Typs für die gegebene Methode zurück.
Die folgende statische Methode wird ebenfalls generiert:
static ServiceDescriptor descriptor(): Gibt den Deskriptor des Typs zurück, der Informationen darüber enthält, welche Methoden dieser Dienst hat und welche Eingabe- und Ausgabetypen sie haben.
Stub
Der Protocol Buffer Compiler generiert auch eine "Stub"-Implementierung jeder Service-Schnittstelle, die von Clients verwendet wird, die Anfragen an Server senden möchten, die den Dienst implementieren. Für den Foo-Dienst (wie oben beschrieben) wird die Stub-Implementierung Foo_Stub definiert. Wie bei verschachtelten Nachrichtentypen wird ein typedef verwendet, damit Foo_Stub auch als Foo::Stub bezeichnet werden kann.
Foo_Stub ist eine Unterklasse von Foo, die auch die folgenden Methoden implementiert:
Foo_Stub(RpcChannel* channel): Erstellt einen neuen Stub, der Anfragen über den angegebenen Kanal sendet.Foo_Stub(RpcChannel* channel, ChannelOwnership ownership): Erstellt einen neuen Stub, der Anfragen über den angegebenen Kanal sendet und diesen Kanal möglicherweise besitzt. WennownershipService::STUB_OWNS_CHANNEList, wird der Kanal gelöscht, wenn das Stub-Objekt gelöscht wird.RpcChannel* channel(): Gibt den Kanal dieses Stubs zurück, wie im Konstruktor übergeben.
Der Stub implementiert zusätzlich jede der Methoden des Dienstes als Wrapper um den Kanal. Das Aufrufen einer der Methoden ruft einfach channel->CallMethod() auf.
Die Protocol Buffer-Bibliothek enthält keine RPC-Implementierung. Sie enthält jedoch alle Werkzeuge, die Sie benötigen, um eine generierte Dienstklasse mit jeder beliebigen RPC-Implementierung Ihrer Wahl zu verbinden. Sie müssen nur Implementierungen von RpcChannel und RpcController bereitstellen. Weitere Informationen finden Sie in der Dokumentation zu service.h.
Plugin-Einfügepunkte
Code-Generator-Plugins, die die Ausgabe des C++-Code-Generators erweitern möchten, können Code der folgenden Typen über die angegebenen Einfügepunkt-Namen einfügen. Jeder Einfügepunkt erscheint in sowohl der .pb.cc-Datei als auch der .pb.h-Datei, sofern nicht anders angegeben.
includes: Include-Direktiven.namespace_scope: Deklarationen, die in das Paket/den Namespace der Datei gehören, aber nicht innerhalb einer bestimmten Klasse. Erscheint nach allem anderen Code im Namespace.global_scope: Deklarationen, die auf oberster Ebene außerhalb des Namespaces der Datei gehören. Erscheint ganz am Ende der Datei.class_scope:TYPENAME: Mitgliedsdeklarationen, die in eine Nachrichtenkasse gehören.TYPENAMEist der vollständige Proto-Name, z. B.package.MessageType. Erscheint nach allen anderen öffentlichen Deklarationen in der Klasse. Dieser Einfügepunkt erscheint nur in der.pb.h-Datei.
Generieren Sie keinen Code, der von privaten Klassenmitgliedern abhängt, die vom Standard-Code-Generator deklariert werden, da sich diese Implementierungsdetails in zukünftigen Versionen von Protocol Buffers ändern können.