C++ Generierter Code Leitfaden

Beschreibt genau, welchen C++-Code der Protocol-Buffer-Compiler für jede gegebene Protokoll-Definition generiert.

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.h bzw. .pb.cc fü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 der text_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. Wenn option optimize_for = LITE_RUNTIME in der .proto-Datei angegeben ist, ändert sich der Rückgabetyp zu std::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. Wenn option optimize_for = LITE_RUNTIME in der .proto-Datei angegeben ist, ändert sich der Rückgabetyp zu std::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 von Foo zurück, die identisch mit einer neu erstellten Instanz von Foo ist (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 ihre New()-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: Gibt true zurü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 gibt has_foo() true zurück und foo() gibt value zurück.
  • void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt has_foo() false zurück und foo() 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 gibt foo() value zurück.
  • void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt foo() 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: Gibt true zurü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 gibt has_foo() true zurück und foo() gibt eine Kopie von value zurück.

  • string* mutable_foo(): Gibt einen Zeiger auf das veränderliche string-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 gibt has_foo() true zurück und foo() 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 gibt has_foo() false zurück und foo() gibt den Standardwert zurück.

  • void set_allocated_foo(string* value): Weist das string-Objekt dem Feld zu und gibt den vorherigen Feldwert frei, falls vorhanden. Wenn der string-Zeiger nicht NULL ist, übernimmt die Nachricht den Besitz des zugewiesenen string-Objekts und has_foo() gibt true zurück. Die Nachricht kann das zugewiesene string-Objekt jederzeit löschen, daher können Referenzen auf das Objekt ungültig werden. Wenn der value NULL ist, ist das Verhalten dasselbe wie beim Aufrufen von clear_foo().

  • string* release_foo(): 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() gibt false zurück und foo() 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 gibt foo() eine Kopie von value zurück.
  • string* mutable_foo(): Gibt einen Zeiger auf das veränderliche string-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 gibt foo() 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 gibt foo() den leeren String/die leeren Bytes zurück.
  • void set_allocated_foo(string* value): Weist das string-Objekt dem Feld zu und gibt den vorherigen Feldwert frei, falls vorhanden. Wenn der string-Zeiger nicht NULL ist, übernimmt die Nachricht den Besitz des zugewiesenen string-Objekts. Die Nachricht kann das zugewiesene string-Objekt jederzeit löschen, daher können Referenzen auf das Objekt ungültig werden. Wenn der value NULL ist, ist das Verhalten dasselbe wie beim Aufrufen von clear_foo().
  • string* release_foo(): 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 und foo() 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 leerer Cord (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 gibt foo() value zurück.
  • void set_foo(::absl::string_view value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibt foo() value als absl::Cord zurück.
  • void clear_foo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt foo() einen leeren Cord (proto3) oder den Standardwert (proto2 und Editionen) zurück.
  • bool has_foo(): Gibt true zurück, wenn das Feld gesetzt ist. Gilt nur für das Feld optional in 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: Gibt true zurü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 gibt has_bar() true zurück und bar() gibt value zurück. Im Debug-Modus (d.h. NDEBUG ist nicht definiert) wird der Prozess abgebrochen, wenn value keinem der für Bar definierten Werte entspricht.
  • void clear_bar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt has_bar() false zurück und bar() 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 gibt bar() value zurück.
  • void clear_bar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt bar() 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: Gibt true zurü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 eine Bar zurückgegeben, bei der keine Felder gesetzt sind (möglicherweise Bar::default_instance()).
  • Bar* mutable_bar(): Gibt einen Zeiger auf das veränderliche Bar-Objekt zurück, das den Wert des Feldes speichert. Wenn das Feld vor dem Aufruf nicht gesetzt war, dann hat die zurückgegebene Bar keine gesetzten Felder (d.h. sie ist identisch mit einer neu zugewiesenen Bar). Nach dem Aufruf dieser Methode gibt has_bar() true zurück und bar() gibt eine Referenz auf dieselbe Instanz von Bar zurück.
  • void clear_bar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt has_bar() false zurück und bar() gibt den Standardwert zurück.
  • void set_allocated_bar(Bar* value): Weist das Bar-Objekt dem Feld zu und gibt den vorherigen Feldwert frei, falls vorhanden. Wenn der Bar-Zeiger nicht NULL ist, übernimmt die Nachricht den Besitz des zugewiesenen Bar-Objekts und has_bar() gibt true zurück. Wenn der Bar NULL ist, ist das Verhalten dasselbe wie beim Aufrufen von clear_bar().
  • Bar* release_bar(): Gibt den Besitz des Feldes frei und gibt den Zeiger des Bar-Objekts zurück. Nach dem Aufruf dieser Methode übernimmt der Aufrufer den Besitz des zugewiesenen Bar-Objekts, has_bar() gibt false zurück und bar() 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 die empty()-Methode im zugrunde liegenden RepeatedField anstelle 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 gibt foo_size() Null zurück.
  • const RepeatedField<int32_t>& foo() const: Gibt das zugrunde liegende RepeatedField zurü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änderliche RepeatedField zurü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 die empty()-Methode im zugrunde liegenden RepeatedField anstelle 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änderliche string-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 gibt foo_size() Null zurück.
  • const RepeatedPtrField<string>& foo() const: Gibt das zugrunde liegende RepeatedPtrField zurü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änderliche RepeatedPtrField zurü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 die empty()-Methode im zugrunde liegenden RepeatedField anstelle 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, wenn value keinem der für Bar definierten 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, wenn value keinem der für Bar definierten Werte entspricht.
  • void clear_bar(): Entfernt alle Elemente aus dem Feld. Nach dem Aufruf dieser Methode gibt bar_size() Null zurück.
  • const RepeatedField<int>& bar() const: Gibt das zugrunde liegende RepeatedField zurü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änderliche RepeatedField zurü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 die empty()-Methode im zugrunde liegenden RepeatedField anstelle 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änderliche Bar-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ückgegebene Bar ist veränderlich und hat keine gesetzten Felder (d.h. sie ist identisch mit einer neu zugewiesenen Bar).
  • void clear_bar(): Entfernt alle Elemente aus dem Feld. Nach dem Aufruf dieser Methode gibt bar_size() Null zurück.
  • const RepeatedPtrField<Bar>& bar() const: Gibt das zugrunde liegende RepeatedPtrField zurü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änderliche RepeatedPtrField zurü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: Gibt true zurück, wenn der Oneof-Fall kFoo ist.
  • int32 foo() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-Fall kFoo ist. 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 dann true zurück, foo() gibt value zurück und example_name_case() gibt kFoo zurück.
  • void clear_foo():
    • Wenn der Oneof-Fall nicht kFoo ist, wird nichts geändert.
    • Wenn der Oneof-Fall kFoo ist, wird der Wert des Feldes und der Oneof-Fall gelöscht. has_foo() gibt dann false zurück, foo() gibt den Standardwert zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück.

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: Gibt true zurück, wenn der Oneof-Fall kFoo ist.
  • const string& foo() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-Fall kFoo ist. 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 dann true zurück, foo() gibt eine Kopie von value zurück und example_name_case() gibt kFoo zurück.
  • void set_foo(const string& value): Ähnlich wie das erste set_foo(), kopiert aber von einer const String-Referenz.
  • void set_foo(string&& value): Ähnlich wie das erste set_foo(), verschiebt aber den übergebenen String.
  • void set_foo(const char* value): Ähnlich wie das erste set_foo(), kopiert aber von einem C-Style-String mit Nullterminierung.
  • void set_foo(const char* value, int size): Ähnlich wie das erste set_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 kFoo und gibt einen Zeiger auf das veränderliche String-Objekt zurück, das den Wert des Feldes speichert. Wenn der Oneof-Fall vor dem Aufruf nicht kFoo war, ist der zurückgegebene String leer (nicht der Standardwert).
    • has_foo() gibt dann true zurück, foo() gibt den Wert zurück, der in den gegebenen String geschrieben wurde, und example_name_case() gibt kFoo zurück.
  • void clear_foo():
    • Wenn der Oneof-Fall nicht kFoo ist, werden keine Änderungen vorgenommen.
    • Wenn der Oneof-Fall kFoo ist, wird das Feld freigegeben und der Oneof-Fall gelöscht. has_foo() gibt dann false zurück, foo() gibt den Standardwert zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück.
  • void set_allocated_foo(string* value):
    • Ruft clear_example_name() auf.
    • Wenn der String-Zeiger nicht NULL ist: Weist das String-Objekt dem Feld zu und setzt den Oneof-Fall auf kFoo. Die Nachricht übernimmt den Besitz des zugewiesenen String-Objekts, has_foo() gibt true zurück und example_name_case() gibt kFoo zurück.
    • Wenn der String-Zeiger NULL ist, gibt has_foo() false zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück.
  • string* release_foo():
    • Gibt NULL zurück, wenn der Oneof-Fall nicht kFoo ist.
    • 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() gibt false zurück, foo() gibt den Standardwert zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück.

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: Gibt true zurück, wenn der Oneof-Fall kBar ist.
  • Bar bar() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-Fall kBar ist. 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() gibt true zurück, bar() gibt value zurück und example_name_case() gibt kBar zurück.
    • Im Debug-Modus (d. h. NDEBUG ist nicht definiert), wenn value nicht mit einem der für Bar definierten Werte übereinstimmt und es sich um ein geschlossenes Enum handelt, wird der Prozess abgebrochen.
  • void clear_bar():
    • Es wird nichts geändert, wenn der Oneof-Fall nicht kBar ist.
    • Wenn der Oneof-Fall kBar ist, wird der Wert des Feldes und der Oneof-Fall gelöscht. has_bar() gibt false zurück, bar() gibt den Standardwert zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück.

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-Fall kBar ist.
  • const Bar& bar() const: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-Fall kBar ist. Andernfalls wird eine Bar zurückgegeben, bei der keine Felder gesetzt sind (möglicherweise Bar::default_instance()).
  • Bar* mutable_bar():
    • Wenn ein anderes Feld in demselben Oneof gesetzt ist, wird clear_example_name() aufgerufen.
    • Setzt den Oneof-Fall auf kBar und gibt einen Zeiger auf das modifizierbare Bar-Objekt zurück, das den Feldwert speichert. Wenn der Oneof-Fall vor dem Aufruf nicht kBar war, 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() true zurück, bar() gibt eine Referenz auf dieselbe Instanz von Bar zurück und example_name_case() gibt kBar zurück.
  • void clear_bar():
    • Es wird nichts geändert, wenn der Oneof-Fall nicht kBar ist.
    • Wenn der Oneof-Fall gleich kBar ist, wird das Feld freigegeben und der Oneof-Fall gelöscht. has_bar() gibt false zurück, bar() gibt den Standardwert zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück.
  • void set_allocated_bar(Bar* bar):
    • Ruft clear_example_name() auf.
    • Wenn der Bar-Zeiger nicht NULL ist: Setzt das Bar-Objekt auf das Feld und setzt den Oneof-Fall auf kBar. Die Nachricht übernimmt das Eigentum an dem zugewiesenen Bar-Objekt, has_bar() gibt true zurück und example_name_case() gibt kBar zurück.
    • Wenn der Zeiger NULL ist, gibt has_bar() false zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück. (Das Verhalten ist wie beim Aufruf von clear_example_name())
  • Bar* release_bar():
    • Gibt NULL zurück, wenn der Oneof-Fall nicht kBar ist.
    • Wenn der Oneof-Fall kBar ist, wird der Oneof-Fall gelöscht, das Eigentum am Feld freigegeben und der Zeiger auf das Bar-Objekt zurückgegeben. Nach diesem Aufruf übernimmt der Aufrufer das Eigentum am zugewiesenen Bar-Objekt, has_bar() gibt false zurück, bar() gibt den Standardwert zurück und example_name_case() gibt EXAMPLE_NAME_NOT_SET zurück.

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änderliche Map zurück.
  • google::protobuf::Map<int32, int32>* mutable_weight();: Gibt eine veränderliche Map zurü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.

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. Gibt EXAMPLE_NAME_NOT_SET zurü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 auf EXAMPLE_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): Gibt true zurück, wenn der angegebene numerische Wert mit einem der definierten Werte von Foo übereinstimmt. Im obigen Beispiel wäre dies true, 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ürde Foo_Name(5) "VALUE_B" zurückgeben.
  • bool Foo_Parse(::absl::string_view name, Foo* value): Wenn name ein gültiger Wertname für dieses Enum ist, wird dieser Wert in value zugewiesen und true zurückgegeben. Andernfalls wird false zurückgegeben. Im obigen Beispiel würde Foo_Parse("VALUE_C", &some_foo) true zurückgeben und some_foo auf 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 als Foo_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 den ServiceDescriptor des 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.
  • GetRequestPrototype und GetResponsePrototype: 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. Wenn ownership Service::STUB_OWNS_CHANNEL ist, 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. TYPENAME ist 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.