Function declaration
Eine Funktionsdeklaration führt den Funktionsnamen und seinen Typ ein. Eine Funktionsdefinition verknüpft den Funktionsnamen/Typ mit dem Funktionskörper.
Inhaltsverzeichnis |
Funktionsdeklaration
Funktionsdeklarationen können in jedem Gültigkeitsbereich erscheinen. Eine Funktionsdeklaration im Klassenbereich führt eine Klassenmitgliedsfunktion ein (sofern nicht das friend -Spezifizierer verwendet wird), siehe member functions und friend functions für Details.
noptr-declarator
(
parameter-list
)
cv
(optional)
ref
(optional)
except
(optional)
attr
(optional)
|
(1) | ||||||||
noptr-declarator
(
parameter-list
)
cv
(optional)
ref
(optional)
except
(optional)
attr
(optional)
->
trailing
|
(2) | (seit C++11) | |||||||
(siehe Declarations für die anderen Formen der declarator Syntax)
| noptr-declarator | - |
jeder gültige
declarator
, aber wenn er mit
*
,
&
oder
&&
beginnt, muss er in Klammern eingeschlossen sein.
|
||||||
| parameter-list | - | möglicherweise leere, kommagetrennte Liste der Funktionsparameter (siehe unten für Details) | ||||||
| attr | - | (seit C++11) eine Liste von Attributen . Diese Attribute werden auf den Typ der Funktion angewendet, nicht auf die Funktion selbst. Die Attribute für die Funktion erscheinen nach dem Bezeichner innerhalb des Deklarators und werden mit den Attributen kombiniert, die am Anfang der Deklaration erscheinen, falls vorhanden. | ||||||
| cv | - | const/volatile-Qualifizierung, nur zulässig in Nicht-statischen Memberfunktionsdeklarationen | ||||||
| ref | - | (seit C++11) Ref-Qualifizierung, nur zulässig in Nicht-statischen Memberfunktionsdeklarationen | ||||||
| except | - |
|
||||||
| trailing | - | Nachgestellter Rückgabetyp, nützlich wenn der Rückgabetyp von Argumentnamen abhängt, wie in template < class T, class U > auto add ( T t, U u ) - > decltype ( t + u ) ; oder kompliziert ist, wie in auto fpif ( int ) - > int ( * ) ( int ) |
|
Wie in Deklarationen erwähnt, kann der Deklarator von einer requires -Klausel gefolgt werden, die die zugehörigen Constraints für die Funktion deklariert. Diese müssen erfüllt sein, damit die Funktion durch Overload Resolution ausgewählt wird. (Beispiel: void f1 ( int a ) requires true ; ) Beachten Sie, dass das zugehörige Constraint Teil der Funktionssignatur, aber nicht Teil des Funktionstyps ist. |
(seit C++20) |
Funktionsdeklaratoren können mit anderen Deklaratoren gemischt werden, wo die Deklarationsspezifizierer-Sequenz es erlaubt:
// deklariert einen int, einen int*, eine Funktion und einen Zeiger auf eine Funktion int a = 1, *p = NULL, f(), (*pf)(double); // decl-specifier-seq ist int // Deklarator f() deklariert (definiert aber nicht) // eine Funktion ohne Argumente, die int zurückgibt struct S { virtual int f(char) const, g(int) &&; // deklariert zwei nicht-statische Memberfunktionen virtual int f(char), x; // Kompilierfehler: virtual (in decl-specifier-seq) // ist nur in Deklarationen von nicht-statischen // Memberfunktionen erlaubt };
|
Die Verwendung eines volatile-qualifizierten Objekttyps als Parametertyp oder Rückgabetyp ist veraltet. |
(since C++20) |
Der Rückgabetyp einer Funktion kann kein Funktionstyp oder Array-Typ sein (kann jedoch ein Zeiger oder eine Referenz auf diese sein).
|
Wie bei jeder Deklaration gelten sowohl Attribute, die vor der Deklaration erscheinen, als auch Attribute, die unmittelbar nach dem Bezeichner innerhalb des Deklarators erscheinen, für die deklarierte oder definierte Entität (in diesem Fall für die Funktion): [[noreturn]] void f [[noreturn]] (); // OK: both attributes apply to the function f Allerdings gelten die Attribute, die nach dem Deklarator erscheinen (in der obigen Syntax), für den Typ der Funktion, nicht für die Funktion selbst: void f() [[noreturn]]; // Error: this attribute has no effect on the function itself |
(seit C++11) |
Rückgabetyp-DeduktionWenn die Deklarator-Spezifizierer-Sequenz der Funktionsdeklaration das Schlüsselwort auto enthält, kann der nachgestellte Rückgabetyp weggelassen werden und wird vom Compiler aus dem Typ des Operanden im nicht-verworfenen return -Statement abgeleitet. Wenn der Rückgabetyp nicht decltype ( auto ) verwendet, folgt die Ableitung den Regeln der Template-Argument-Ableitung : int x = 1; auto f() { return x; } // Rückgabetyp ist int const auto& f() { return x; } // Rückgabetyp ist const int&
Wenn der Rückgabetyp
decltype
(
auto
)
ist, entspricht der Rückgabetyp dem, was erhalten würde, wenn der Operand in der return-Anweisung in
int x = 1; decltype(auto) f() { return x; } // Rückgabetyp ist int, gleich wie decltype(x) decltype(auto) f() { return(x); } // Rückgabetyp ist int&, gleich wie decltype((x)) (Hinweis: „ const decltype ( auto ) & “ ist ein Fehler, decltype ( auto ) muss alleinstehend verwendet werden) Wenn es mehrere return-Anweisungen gibt, müssen alle auf denselben Typ deduzieren: auto f(bool val) { if (val) return 123; // leitet Rückgabetyp int ab else return 3.14f; // Fehler: leitet Rückgabetyp float ab } Wenn es keine return-Anweisung gibt oder wenn der Operand der return-Anweisung ein void-Ausdruck ist (einschließlich return-Anweisungen ohne Operand), muss der deklarierte Rückgabetyp entweder decltype ( auto ) sein, wobei der abgeleitete Rückgabetyp void ist, oder (möglicherweise cv-qualifiziertes) auto , wobei der abgeleitete Rückgabetyp dann (identisch cv-qualifiziertes) void ist: auto f() {} // gibt void zurück auto g() { return f(); } // gibt void zurück auto* x() {} // Fehler: auto* kann nicht von void abgeleitet werden Sobald eine return-Anweisung in einer Funktion gesehen wurde, kann der von dieser Anweisung abgeleitete Rückgabetyp im Rest der Funktion verwendet werden, einschließlich in anderen return-Anweisungen: auto sum(int i) { if (i == 1) return i; // Rückgabetyp von sum ist int else return sum(i - 1) + i; // OK: Rückgabetyp von sum ist bereits bekannt } Wenn die return-Anweisung eine geschweifte Initialisierungsliste verwendet, ist Deduktion nicht erlaubt: auto func() { return {1, 2, 3}; } // Fehler Virtual functions und coroutines (seit C++20) können keine Rückgabetyp-Ableitung verwenden: struct F { virtual auto f() { return 2; } // Fehler }; Function templates außer user-defined conversion functions können die Rückgabetyp-Ableitung verwenden. Die Ableitung erfolgt bei der Instanziierung, selbst wenn der Ausdruck in der return-Anweisung nicht dependent ist. Diese Instanziierung befindet sich nicht in einem immediate context für SFINAE . template<class T> auto f(T t) { return t; } typedef decltype(f(1)) fint_t; // instanziiert f<int> um den Rückgabetyp abzuleiten template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instanziiert beide fs um Rückgabetypen zu bestimmen, // wählt die zweite Template-Überladung Neudeklarationen oder Spezialisierungen von Funktionen oder Funktionsvorlagen, die Rückgabetypableitung verwenden, müssen dieselben Rückgabetyp-Platzhalter verwenden: auto f(int num) { return num; } // int f(int num); // Fehler: kein Platzhalter-Rückgabetyp // decltype(auto) f(int num); // Fehler: anderer Platzhalter template<typename T> auto g(T t) { return t; } template auto g(int); // OK: Rückgabetyp ist int // template char g(char); // Fehler: keine Spezialisierung des Primär-Templates g Ebenso dürfen Neudeklarationen oder Spezialisierungen von Funktionen oder Funktionsvorlagen, die keine Rückgabetypableitung verwenden, keinen Platzhalter verwenden: int f(int num); // auto f(int num) { return num; } // Fehler: keine Neudeklaration von f template<typename T> T g(T t) { return t; } template int g(int); // OK: Spezialisierung von T als int // template auto g(char); // Fehler: keine Spezialisierung des primären Templates g Explizite Instanziierungsdeklarationen instanziieren selbst keine Funktions-Templates, die Rückgabetypableitung verwenden: template<typename T> auto f(T t) { return t; } extern template auto f(int); // instanziiert f<int> nicht int (*p)(int) = f; // instanziiert f<int>, um dessen Rückgabetyp zu bestimmen, // aber eine explizite Instanziierungsdefinition // ist weiterhin irgendwo im Programm erforderlich |
(seit C++14) |
Parameterliste
Die Parameterliste bestimmt die Argumente, die beim Aufruf der Funktion angegeben werden können. Es handelt sich um eine durch Kommas getrennte Liste von Parameterdeklarationen , von denen jede die folgende Syntax hat:
| attr (optional) decl-specifier-seq declarator | (1) | ||||||||
|
attr
(optional)
|
(2) | (seit C++23) | |||||||
attr
(optional)
decl-specifier-seq
declarator
=
initializer
|
(3) | ||||||||
| attr (optional) decl-specifier-seq abstract-declarator (optional) | (4) | ||||||||
|
attr
(optional)
|
(5) | (seit C++23) | |||||||
attr
(optional)
decl-specifier-seq
abstract-declarator
(optional)
=
initializer
|
(6) | ||||||||
void
|
(7) | ||||||||
| Falsche Verwendung | Beispiel |
|---|---|
| mehrere Parameter vorhanden sind | int f1 ( void , int ) ; |
| der void -Parameter benannt ist | inf f2 ( void param ) ; |
| void CV-qualifiziert ist | int f3 ( const void ) ; |
| void ein abhängiger Name ist |
int
f4
(
T
)
;
(wobei
T
void
ist)
|
| der void -Parameter ein expliziter Objektparameter ist (seit C++23) | int f5 ( this void ) ; |
|
Obwohl decl-specifier-seq impliziert, dass es Spezifizierer außer Typspezifizierern geben kann, ist der einzige andere erlaubte Spezifizierer register sowie auto (bis C++11) , und er hat keine Wirkung. |
(bis C++17) |
|
Falls einer der Funktionsparameter einen Platzhalter (entweder auto oder einen Konzepttyp ) verwendet, ist die Funktionsdeklaration stattdessen eine Abgekürzte Funktions-Template-Deklaration : void f1(auto); // same as template<class T> void f1(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept |
(seit C++20) |
|
Eine Parameterdeklaration mit dem Spezifizierer this (Syntax ( 2 ) / ( 5 ) ) deklariert einen Expliziten Objektparameter . Ein expliziter Objektparameter kann kein Funktionsparameter-Pack sein und kann nur als erster Parameter der Parameterliste in den folgenden Deklarationen erscheinen:
Eine Elementfunktion mit einem expliziten Objektparameter unterliegt den folgenden Einschränkungen:
struct C { void f(this C& self); // OK template<typename Self> void g(this Self&& self); // also OK for templates void p(this C) const; // Error: “const” not allowed here static void q(this C); // Error: “static” not allowed here void r(int, this C); // Error: an explicit object parameter // can only be the first parameter }; // void func(this C& self); // Error: non-member functions cannot have // an explicit object parameter |
(seit C++23) |
Parameternamen, die in Funktionsdeklarationen deklariert werden, dienen in der Regel nur der Selbst-Dokumentation. Sie werden (bleiben aber optional) in Funktionsdefinitionen verwendet.
Eine Mehrdeutigkeit entsteht in einer Parameterliste, wenn ein Typname in Klammern geschachtelt ist (einschließlich Lambda-Ausdrücken ) (seit C++11) . In diesem Fall besteht die Wahl zwischen der Deklaration eines Parameters vom Typ Zeiger auf Funktion und der Deklaration eines Parameters mit redundanten Klammern um den Bezeichner des Deklarators . Die Auflösung besteht darin, den Typnamen als einen einfachen Typspezifizierer (welcher der Zeiger-auf-Funktion-Typ ist) zu betrachten:
class C {}; void f(int(C)) {} // void f(int(*fp)(C param)) {} // NICHT void f(int C) {} void g(int *(C[10])); // void g(int *(*fp)(C param[10])); // NICHT void g(int *C[10]);
Parametertyp darf kein Typ sein, der eine Referenz oder einen Zeiger auf ein Array unbekannter Größe enthält, einschließlich mehrstufiger Zeiger/Arrays solcher Typen oder eines Zeigers auf Funktionen, deren Parameter solche Typen sind.
Verwendung einer Ellipse
Der letzte Parameter in der Parameterliste kann eine Ellipse sein ( ... ); dies deklariert eine variadische Funktion . Das Komma vor der Ellipse kann weggelassen werden (in C++26 als veraltet markiert) :
int printf(const char* fmt, ...); // eine variadische Funktion int printf(const char* fmt...); // wie oben, aber seit C++26 veraltet template<typename... Args> void f(Args..., ...); // eine variadische Funktionsvorlage mit einem Parameterpaket template<typename... Args> void f(Args... ...); // wie oben, aber seit C++26 veraltet template<typename... Args> void f(Args......); // wie oben, aber seit C++26 veraltet
Funktionstyp
Parameter-Typen-Liste
Die parameter-type-list einer Funktion wird wie folgt bestimmt:
- Der Typ jedes Parameters (einschließlich Parameter Packs ) (seit C++11) wird aus seiner eigenen Parameterdeklaration bestimmt.
-
Nach der Bestimmung des Typs jedes Parameters wird jeder Parameter vom Typ "Array von
T" oder vom FunktionstypTzu "Zeiger aufT" angepasst. - Nach der Erstellung der Liste der Parametertypen werden alle CV-Qualifizierer auf oberster Ebene, die einen Parametertyp modifizieren, bei der Bildung des Funktionstyps gelöscht.
- Die resultierende Liste transformierter Parametertypen und das Vorhandensein oder Fehlen der Ellipse oder eines Parameter Packs (seit C++11) bilden die Parameter-Typen-Liste der Funktion.
void f(char*); // #1 void f(char[]) {} // definiert #1 void f(const char*) {} // OK, weitere Überladung void f(char* const) {} // Fehler: definiert #1 neu void g(char(*)[2]); // #2 void g(char[3][2]) {} // definiert #2 void g(char[3][3]) {} // OK, weitere Überladung void h(int x(const int)); // #3 void h(int (*)(int)) {} // definiert #3
Bestimmung des Funktionstyps
In der Syntax
(1)
, unter der Annahme, dass
noptr-declarator
als eigenständige Deklaration betrachtet wird, wird der Typ der
qualified-id
oder
unqualified-id
in
noptr-declarator
als "derived-declarator-type-list
T
" angegeben:
|
(seit C++17) |
-
Der
(bis C++17)
Andernfalls ist der
(seit C++17)
Typ der deklarierten Funktion
„abgeleiteter-Deklarator-Typen-Liste Funktion von
Parameter-Typen-Liste cv (optional) ref (optional) (seit C++11) zurückgebendT“.
|
In Syntax
(2)
, unter der Annahme, dass
noptr-declarator
als eigenständige Deklaration betrachtet wird, und mit dem Typ der
qualified-id
oder
unqualified-id
in
noptr-declarator
als "derived-declarator-type-list
|
(seit C++11) |
|
(seit C++17) |
attr , falls vorhanden, gilt für den Funktionstyp. |
(seit C++11) |
// der Typ von "f1" ist // "Funktion von int mit Rückgabe void, mit Attribut noreturn" void f1(int a) [[noreturn]]; // der Typ von "f2" ist // "constexpr noexcept Funktion von Zeiger auf int mit Rückgabe int" constexpr auto f2(int[] b) noexcept -> int; struct X { // der Typ von "f3" ist // "Funktion ohne Parameter const mit Rückgabe const int" const int f3() const; };
Nachgestellte Qualifizierer
Ein Funktionstyp mit
cv
oder
ref
(seit C++11)
(einschließlich eines durch
typedef
benannten Typs) kann nur erscheinen als:
- der Funktionstyp für eine nicht-statische Elementfunktion ,
- der Funktionstyp, auf den ein Zeiger auf Element verweist,
- der oberste Funktionstyp einer Funktions- typedef Deklaration oder Aliasdeklaration (seit C++11) ,
- die Typ-ID im Standardargument eines Template-Typparameters , oder
- die Typ-ID eines Template-Arguments für einen Template-Typparameter.
typedef int FIC(int) const; FIC f; // Fehler: deklariert keine Memberfunktion struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK
Funktionssignatur
Jede Funktion hat eine Signatur.
Die Signatur einer Funktion besteht aus ihrem Namen und der Parameter-Type-List . Ihre Signatur enthält auch den umschließenden Namespace , mit folgenden Ausnahmen:
- Wenn die Funktion eine Member-Funktion ist, enthält ihre Signatur die Klasse, zu der die Funktion gehört, anstelle des umschließenden Namensraums. Ihre Signatur enthält auch die folgenden Komponenten, falls vorhanden:
-
- cv
|
(seit C++11) |
|
(seit C++20) |
except und attr (seit C++11) betrifft nicht die Funktionssignatur , obwohl noexcept Spezifikation den Funktionstyp beeinflusst (seit C++17) .
Funktionsdefinition
Eine Nicht-Mitgliedsfunktionsdefinition darf nur im Namensbereich auftreten (es gibt keine geschachtelten Funktionen). Eine Mitgliedsfunktionsdefinition kann auch im Rumpf einer Klassendefinition erscheinen. Sie haben folgende Syntax:
|
attr
(optional)
decl-specifier-seq
(optional)
declarator
virt-specs (optional) contract-specs (optional) function-body |
(1) | ||||||||
|
attr
(optional)
decl-specifier-seq
(optional)
declarator
requires-clause contract-specs (optional) function-body |
(2) | (seit C++20) | |||||||
| attr | - | (seit C++11) eine Liste von Attributen . Diese Attribute werden mit den Attributen nach dem Bezeichner im Deklarator kombiniert (siehe oben auf dieser Seite), falls vorhanden. |
| decl-specifier-seq | - | der Rückgabetyp mit Spezifizierern, wie in der Deklarationsgrammatik |
| declarator | - | Funktionsdeklarator, identisch mit der Funktionsdeklarationsgrammatik oben (kann geklammert sein) |
| virt-specs | - |
(seit C++11)
override
,
final
, oder deren Kombination in beliebiger Reihenfolge
|
| requires-clause | - | eine requires -Klausel |
| contract-specs | - | (seit C++26) eine Liste von Funktionsvertragsspezifizierern |
| function-body | - | der Funktionsrumpf (siehe unten) |
function-body
ist eines der folgenden:
| ctor-initializer (optional) compound-statement | (1) | ||||||||
| function-try-block | (2) | ||||||||
=
default
;
|
(3) | (seit C++11) | |||||||
=
delete
;
|
(4) | (seit C++11) | |||||||
=
delete
(
string-literal
);
|
(5) | (seit C++26) | |||||||
| ctor-initializer | - | Member-Initialisierungsliste , nur in Konstruktoren erlaubt |
| compound-statement | - | die geschweifte Klammern umschließende Anweisungssequenz , die den Rumpf einer Funktion bildet |
| function-try-block | - | ein Funktions- try Block |
| string-literal | - | ein nicht ausgewertetes String-Literal , das zur Erläuterung des Grundes verwendet werden kann, warum die Funktion gelöscht wurde |
int max(int a, int b, int c) { int m = (a > b) ? a : b; return (m > c) ? m : c; } // Deklarator-Spezifizierer-Sequenz ist "int" // Deklarator ist "max(int a, int b, int c)" // Rumpf ist { ... }
Der Funktionsrumpf ist eine compound statement (Folge von null oder mehr Anweisungen, umgeben von einem Paar geschweifter Klammern), die ausgeführt wird, wenn der Funktionsaufruf erfolgt. Darüber hinaus umfasst der Funktionsrumpf eines constructor auch Folgendes:
- Für alle nicht-statischen Datenelemente, deren Bezeichner in der Member-Initialisierungsliste des Konstruktors fehlen, werden die Standard-Member-Initialisierer oder (seit C++11) Standard-Initialisierungen verwendet, um die entsprechenden Member- Unterobjekte zu initialisieren.
- Für alle Basisklassen, deren Typnamen in der Member-Initialisierungsliste des Konstruktors fehlen, werden die Standard-Initialisierungen verwendet, um die entsprechenden Basisklassen-Unterobjekte zu initialisieren.
|
Wenn eine Funktionsdefinition einen virt-specs enthält, muss sie eine Memberfunktion definieren. |
(since C++11) |
|
Wenn eine Funktionsdefinition eine requires-clause enthält, muss sie eine templatierte Funktion definieren. |
(since C++20) |
void f() override {} // Fehler: keine Member-Funktion void g() requires (sizeof(int) == 4) {} // Fehler: keine templatisierte Funktion
Die Parametertypen sowie der Rückgabetyp einer Funktionsdefinition dürfen keine (möglicherweise cv-qualifizierten) unvollständigen Klassentypen sein, es sei denn, die Funktion ist als deleted definiert (seit C++11) . Die Vollständigkeitsprüfung erfolgt nur im Funktionsrumpf, was es Memberfunktionen ermöglicht, die Klasse, in der sie definiert sind (oder ihre umschließende Klasse), zurückzugeben, selbst wenn diese zum Zeitpunkt der Definition unvollständig ist (im Funktionsrumpf ist sie vollständig).
Die in der Deklaration einer Funktionsdefinition deklarierten Parameter sind im Gültigkeitsbereich innerhalb des Funktionskörpers. Wenn ein Parameter im Funktionskörper nicht verwendet wird, muss er nicht benannt werden (es genügt, einen abstrakten Deklarator zu verwenden):
void print(int a, int) // zweiter Parameter wird nicht verwendet { std::printf("a = %d\n", a); }
Obwohl die obersten cv-Qualifizierer in Funktionsdeklarationen auf den Parametern verworfen werden, modifizieren sie den Typ des Parameters, wie er im Rumpf einer Funktion sichtbar ist:
void f(const int n) // deklariert Funktion vom Typ void(int) { // aber im Körper ist der Typ von "n" const int }
Defaultierte FunktionenWenn die Funktionsdefinition von der Syntax ( 3 ) ist, wird die Funktion als explizit defaulted definiert. Eine explizit defaulted Funktion muss eine special member function oder comparison operator function (since C++20) sein und darf keine default argument haben.
Eine explizit default-spezifizierte spezielle Elementfunktion
Wenn sich der Typ von
Eine Funktion, die bei ihrer ersten Deklaration explizit als default deklariert wird, ist implizit inline und ist implizit constexpr, wenn sie eine constexpr-Funktion sein kann. struct S { S(int a = 0) = default; // Fehler: Standardargument void operator=(const S&) = default; // Fehler: Nicht übereinstimmender Rückgabetyp ~S() noexcept(false) = default; // OK, andere Ausnahmespezifikation private: int i; S(S&); // OK, privater Kopierkonstruktor }; S::S(S&) = default; // OK, definiert Kopierkonstruktor Explizit defaulted Funktionen und implizit deklarierte Funktionen werden zusammen als defaulted Funktionen bezeichnet. Ihre tatsächlichen Definitionen werden implizit bereitgestellt, siehe die entsprechenden Seiten für Details. Gelöschte FunktionenWenn die Funktionsdefinition die Syntax ( 4 ) oder ( 5 ) (since C++26) hat, ist die Funktion als explizit gelöscht definiert. Jede Verwendung einer gelöschten Funktion ist fehlerhaft (das Programm wird nicht kompilieren). Dies umfasst Aufrufe, sowohl explizite (mit einem Funktionsaufrufoperator) als auch implizite (ein Aufruf eines gelöschten überladenen Operators, einer speziellen Memberfunktion, einer Allokationsfunktion etc.), das Bilden eines Zeigers oder Zeigers-auf-Member auf eine gelöschte Funktion, und sogar die Verwendung einer gelöschten Funktion in einem Ausdruck, der nicht potenziell ausgewertet wird. Eine nicht-reine virtuelle Memberfunktion kann als gelöscht definiert werden, auch wenn sie implizit ODR-used wird. Eine gelöschte Funktion kann nur durch gelöschte Funktionen überschrieben werden, und eine nicht-gelöschte Funktion kann nur durch nicht-gelöschte Funktionen überschrieben werden.
Wenn die Funktion überladen ist, Overload Resolution findet zuerst statt, und das Programm ist nur dann fehlerhaft, wenn die gelöschte Funktion ausgewählt wurde: struct T { void* operator new(std::size_t) = delete; void* operator new[](std::size_t) = delete("new[] is deleted"); // seit C++26 }; T* p = new T; // Fehler: Versuch, gelöschten T::operator new aufzurufen T* p = new T[5]; // Fehler: Versuch, gelöschten T::operator new[] aufzurufen, // gibt eine Diagnosemeldung "new[] is deleted" aus Die gelöschte Definition einer Funktion muss die erste Deklaration in einer Übersetzungseinheit sein: Eine zuvor deklarierte Funktion kann nicht als gelöscht neu deklariert werden: struct T { T(); }; T::T() = delete; // Fehler: Muss bei der ersten Deklaration gelöscht werden Vom Benutzer bereitgestellte FunktionenEine Funktion ist user-provided , wenn sie user-declared ist und nicht explizit defaulted oder deleted bei ihrer ersten Deklaration. Eine user-provided explicitly-defaulted Funktion (d.h. explizit defaulted nach ihrer ersten Deklaration) wird an dem Punkt definiert, an dem sie explizit defaulted wird; wenn eine solche Funktion implizit als deleted definiert wird, ist das Programm ill-formed. Das Deklarieren einer Funktion als defaulted nach ihrer ersten Deklaration kann effiziente Ausführung und prägnante Definition bieten, während sie eine stabile binäre Schnittstelle zu einer sich entwickelnden Codebasis ermöglicht. // Alle speziellen Elementfunktionen von "trivial" sind // jeweils in ihren ersten Deklarationen standardmäßig implementiert, // sie sind nicht benutzerbereitgestellt struct trivial { trivial() = default; trivial(const trivial&) = default; trivial(trivial&&) = default; trivial& operator=(const trivial&) = default; trivial& operator=(trivial&&) = default; ~trivial() = default; }; struct nontrivial { nontrivial(); // erste Deklaration }; // nicht in der ersten Deklaration standardmäßig implementiert, // ist benutzerbereitgestellt und wird hier definiert nontrivial::nontrivial() = default; Auflösung von Mehrdeutigkeiten
Im Falle einer Mehrdeutigkeit zwischen einem Funktionsrumpf und einem
Initialisierer
, der mit
using T = void(); // Funktions-Typ using U = int; // Nicht-Funktions-Typ T a{}; // definiert eine Funktion, die nichts tut U b{}; // wertinitialisiert ein int-Objekt T c = delete("hello"); // definiert eine Funktion als gelöscht U d = delete("hello"); // kopierinitialisiert ein int-Objekt mit // dem Ergebnis eines delete-Ausdrucks (fehlerhaft)
__func__Im Funktionsrumpf ist die funktionslokale vordefinierte Variable __func__ definiert, als ob durch static const char __func__[] = "Funktionsname"; Diese Variable hat Blockgültigkeit und statische Speicherdauer: struct S { S(): s(__func__) {} // OK: Initialisierungsliste ist Teil des Funktionsrumpfs const char* s; }; void f(const char* s = __func__); // Fehler: Parameterliste ist Teil des Deklarators
Code ausführen
Mögliche Ausgabe: Foo Bar Pub ~Bar |
(seit C++11) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
FunktionsvertragsspezifiziererFunktionsdeklarationen und Lambda-Ausdrücke können eine Sequenz von Funktionsvertragsspezifizierern enthalten, jeder Spezifizierer hat folgende Syntax:
`-Tags oder C++-spezifischen Begriffen (wie "pre", "post", "attr", "predicate", "identifier", "result-attr") liegen, wurde gemäß den Anforderungen keine Übersetzung vorgenommen. Die numerischen Bezeichnungen (1), (2), (3) bleiben ebenfalls unverändert, da sie Teil der technischen Dokumentationsstruktur sind.
1)
Führt eine
Vorbedingungsassertion
ein.
2,3)
Führt eine
Postcondition-Assertion
ein.
2)
Die Assertion bindet nicht an das Ergebnis.
3)
Die Assertion bindet an das Ergebnis.
Ein Funktionsvertragsassertion ist eine Vertragsassertion , die einer Funktion zugeordnet ist. Das Prädikat einer Funktionsvertragsassertion wird kontextuell konvertiert zu bool . Die folgenden Funktionen können nicht mit Funktionsvertragsspezifizierern deklariert werden:
Vorbedingungs-AssertionsEine Vorbedingungsassertion ist mit dem Eintritt in eine Funktion verbunden: int divide(int dividend, int divisor) pre(divisor != 0) { return dividend / divisor; } double square_root(double num) pre(num >= 0) { return std::sqrt(num); } Nachbedingungs-ZusicherungenEine Nachbedingungsassertion ist mit dem normalen Verlassen einer Funktion verbunden. Wenn eine Nachbedingungsassertion einen identifier hat, führt der Funktionsvertrag-Spezifizierer identifier als Namen einer result binding der zugehörigen Funktion ein. Eine result binding bezeichnet das Objekt oder die Referenz, die durch den Aufruf dieser Funktion zurückgegeben wird. Der Typ einer result binding ist der Rückgabetyp ihrer zugehörigen Funktion. int absolute_value(int num) post(r : r >= 0) { return std::abs(num); } double sine(double num) post(r : r >= -1.0 && r <= 1.0) { if (std::isnan(num) || std::isinf(num)) // Beenden über eine Exception verursacht niemals Vertragsverletzung throw std::invalid_argument("Ungültiges Argument"); return std::sin(num); } Wenn eine Nachbedingungs-Assertion einen Bezeichner besitzt und der Rückgabetyp der zugehörigen Funktion (möglicherweise cv-qualifiziert) void ist, ist das Programm fehlerhaft: void f() post(r : r > 0); // Fehler: kein Wert kann an „r“ gebunden werden Wenn der deklarierte Rückgabetyp einer nicht-templatisierten Funktion einen Platzhaltertyp enthält, kann eine Nachbedingungsassertion mit einem Bezeichner nur in einer Funktionsdefinition erscheinen: auto g(auto&) post(r : r >= 0); // OK, „g“ ist ein Template auto h() post(r : r >= 0); // Fehler: Rückgabewert kann nicht benannt werden auto k() post(r : r >= 0) // OK, „k“ ist eine Definition { return 0; } Vertragskonsistenz
Eine
Neudeklaration
Wenn eine Deklaration
Zwei contract-specs sind gleich, wenn sie aus denselben Funktionskontraktspezifizierern in derselben Reihenfolge bestehen.
Ein Funktionsvertragsspezifizierer
Wenn diese Bedingung allein aufgrund des Vergleichs zweier Lambda-Ausdrücke, die innerhalb der Prädikate enthalten sind, nicht erfüllt ist, ist keine Diagnose erforderlich. bool b1, b2; void f() pre (b1) pre([]{ return b2; }()); void f(); // OK, Funktionsvertragsspezifizierer ausgelassen void f() pre (b1) pre([]{ return b2; }()); // Fehler: Closures haben unterschiedliche Typen void f() pre (b1); // Fehler: Funktionsvertragsspezifizierer sind unterschiedlich int g() post(r : b1); int g() post(b1); // Fehler: Keine Ergebnisbindung namespace N { void h() pre (b1); bool b1; void h() pre (b1); // Fehler: Funktionsvertragsspezifizierer unterscheiden sich // gemäß der One-Definition-Rule } |
(seit C++26) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Hinweise
Im Falle von Mehrdeutigkeit zwischen einer Variablendeklaration mit der Direktinitialisierungssyntax und einer Funktionsdeklaration wählt der Compiler immer die Funktionsdeklaration; siehe direct-initialization .
| Feature-Test-Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_decltype_auto
|
201304L
|
(C++14) |
decltype(auto)
|
__cpp_return_type_deduction
|
201304L
|
(C++14) | Rückgabetyp-Deduktion für normale Funktionen |
__cpp_explicit_this_parameter
|
202110L
|
(C++23) | explizite Objektparameter ( deducing this ) |
__cpp_deleted_function
|
202403L
|
(C++26) | gelöschte Funktion mit Begründung |
Schlüsselwörter
Beispiel
#include <iostream> #include <string> // einfache Funktion mit einem Standardargument, gibt nichts zurück void f0(const std::string& arg = "world!") { std::cout << "Hello, " << arg << '\n'; } // die Deklaration befindet sich im Namensbereich (Dateibereich) // (die Definition wird später bereitgestellt) int f1(); // Funktion, die einen Zeiger auf f0 zurückgibt, pre-C++11 Stil void (*fp03())(const std::string&) { return f0; } // Funktion, die einen Zeiger auf f0 zurückgibt, mit C++11 Trailing Return Type auto fp11() -> void(*)(const std::string&) { return f0; } int main() { f0(); fp03()("test!"); fp11()("again!"); int f2(std::string) noexcept; // Deklaration im Funktionsbereich std::cout << "f2(\"bad\"): " << f2("bad") << '\n'; std::cout << "f2(\"42\"): " << f2("42") << '\n'; } // einfache Nicht-Member-Funktion, die int zurückgibt int f1() { return 007; } // Funktion mit einer Exception-Spezifikation und einem Function-Try-Block int f2(std::string str) noexcept try { return std::stoi(str); } catch (const std::exception& e) { std::cerr << "stoi() failed!\n"; return 0; } // gelöschte Funktion, ein Aufrufversuch führt zu einem Kompilierungsfehler void bar() = delete # if __cpp_deleted_function ("reason") # endif ;
Mögliche Ausgabe:
stoi() failed!
Hello, world!
Hello, test!
Hello, again!
f2("bad"): 0
f2("42"): 42
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 135 | C++98 |
in der Klasse definierte Memberfunktionen
konnten keinen Parameter ihres eigenen Typs haben oder diesen zurückgeben, da er unvollständig ist |
erlaubt |
| CWG 332 | C++98 | ein Parameter konnte cv-qualifizierten void Typ haben | verboten |
| CWG 393 | C++98 |
Typen, die Zeiger/Referenzen auf Arrays
unbekannter Größe enthalten, konnten keine Parameter sein |
solche Typen sind erlaubt |
| CWG 452 | C++98 | Member-Initialisiererliste war kein Teil des Funktionsrumpfs | ist es |
| CWG 577 | C++98 |
abhängiger Typ
void
konnte verwendet werden,
um eine Funktion ohne Parameter zu deklarieren |
nur nicht-abhängiger
void ist erlaubt |
| CWG 1327 | C++11 |
defaulted oder deleted Funktionen konnten nicht
mit override oder final spezifiziert werden |
erlaubt |
| CWG 1355 | C++11 | nur spezielle Memberfunktionen konnten user-provided sein | auf alle Funktionen erweitert |
| CWG 1394 | C++11 |
deleted Funktionen konnten keinen Parameter eines
unvollständigen Typs haben oder einen unvollständigen Typ zurückgeben |
unvollständiger Typ erlaubt |
| CWG 1824 | C++98 |
die Vollständigkeitsprüfung von Parametertyp und
Rückgabetyp einer Funktionsdefinition konnte außerhalb des Kontexts der Funktionsdefinition erfolgen |
nur Prüfung im
Kontext der Funktionsdefinition |
| CWG 1877 | C++14 | Rückgabetyp-Deduktion behandelte return ; als return void ( ) ; |
leitet den Rückgabetyp
einfach als void in diesem Fall ab |
| CWG 2015 | C++11 |
die implizite odr-Verwendung einer gelöschten
virtuellen Funktion war ill-formed |
solche odr-Verwendungen sind
von der Nutzungseinschränkung ausgenommen |
| CWG 2044 | C++14 |
Rückgabetyp-Deduktion bei Funktionen, die
void
zurückgeben,
würde fehlschlagen, wenn der deklarierte Rückgabetyp decltype ( auto ) ist |
die Deduktionsregel
für diesen Fall aktualisiert |
| CWG 2081 | C++14 |
Funktionsneudeklarationen konnten Rückgabetyp-
Deduktion verwenden, selbst wenn die ursprüngliche Deklaration dies nicht tat |
nicht erlaubt |
| CWG 2144 | C++11 | { } konnte gleichzeitig ein Funktionsrumpf oder ein Initialisierer sein |
wird durch den Typ
des Deklarator-Identifiers unterschieden |
| CWG 2145 | C++98 | der Deklarator in der Funktionsdefinition konnte nicht in Klammern gesetzt werden | erlaubt |
| CWG 2259 | C++11 |
die Mehrdeutigkeitsauflösungsregel bezüglich geklammerter
Typnamen deckte keine Lambda-Ausdrücke ab |
abgedeckt |
| CWG 2430 | C++98 |
in der Definition einer Memberfunktion in einer Klassendefinition
konnte der Typ dieser Klasse nicht der Rückgabetyp oder Parametertyp sein aufgrund der Auflösung von CWG Issue 1824 |
nur Prüfung im
Funktionsrumpf |
| CWG 2760 | C++98 |
der Funktionsrumpf eines Konstruktors beinhaltete nicht die Initialisierungen,
die nicht im regulären Funktionsrumpf des Konstruktors spezifiziert waren |
beinhaltet auch diese
Initialisierungen |
| CWG 2831 | C++20 |
eine Funktionsdefinition mit einer
Requires-Klausel
könnte eine nicht-templatisierte Funktion definieren |
verboten |
| CWG 2846 | C++23 | explizite Objekt-Memberfunktionen konnten keine Out-of-Class-Definitionen haben | erlaubt |
| CWG 2915 | C++23 | unbenannte explizite Objektparameter konnten Typ void haben | verboten |
Siehe auch
|
C-Dokumentation
für
Funktionsdeklarationen
|