Freigeben über


Explizit vorgegebene und gelöschte Funktionen

In C++11 erhalten Sie durch Defaulted- und Deleted-Funktionen explizite Kontrolle darüber, ob die speziellen Memberfunktionen automatisch generiert werden. Gelöschte Funktionen bieten Ihnen auch eine einfache Sprache, um zu verhindern, dass problematische Typenherstufungen in Argumenten zu Funktionen aller Typen auftreten – spezielle Memberfunktionen und normale Memberfunktionen und nichtemberische Funktionen – was andernfalls zu einem unerwünschten Funktionsaufruf führen würde.

Vorteile von expliziten Defaulted- und Deleted-Funktionen

In C++ generiert der Compiler automatisch den Standardkonstruktor, den Kopierkonstruktor, den Kopierzuordnungsoperator und den Destruktor für einen Typ, wenn er nicht seine eigene deklariert. Diese Funktionen werden als spezielle Memberfunktionen bezeichnet und machen das Verhalten einfacher benutzerdefinierter Typen in C++ wie Strukturen in C. Das heißt, Sie können sie ohne zusätzlichen Codierungsaufwand erstellen, kopieren und zerstören. C++11 stellt Verschiebesemantik für die Sprache bereit und fügt den Bewegungskonstruktor und den Bewegungszuweisungsoperator zur Liste der speziellen Memberfunktionen hinzu, die der Compiler automatisch generieren kann.

Dies ist für einfache Typen praktisch, komplexe Typen definieren jedoch häufig mindestens eine der speziellen Memberfunktionen selbst, wodurch verhindert werden kann, dass andere spezielle Memberfunktionen automatisch generiert werden. In der Praxis:

  • Wenn ein Konstruktor explizit deklariert wird, wird kein Standardkonstruktor automatisch generiert.

  • Wenn ein virtueller Destruktor explizit deklariert wird, wird kein Standarddestruktor automatisch generiert.

  • Wenn ein Bewegungskonstruktor oder Bewegungszuweisungsoperator explizit deklariert wird, tritt Folgendes ein:

    • Es wird kein Kopierkonstruktor automatisch generiert.

    • Es wird kein Kopierzuweisungsoperator automatisch generiert.

  • Wenn ein Kopierkonstruktor, Kopierzuweisungsoperator, Bewegungskonstruktor, Bewegungszuweisungsoperator oder Destruktor explizit deklariert wird, tritt Folgendes ein:

    • Es wird kein Bewegungskonstruktor automatisch generiert.

    • Es wird kein Bewegungszuweisungsoperator automatisch generiert.

Hinweis

Darüber hinaus gibt der C++11-Standard die folgenden zusätzlichen Regeln an:

  • Wenn ein Kopierkonstruktor oder Destruktor explizit deklariert wird, wird die automatische Generierung des Kopierzuweisungsoperators abgelehnt.
  • Wenn ein Kopierzuweisungsoperator oder Destruktor explizit deklariert wird, wird die automatische Generierung eines Kopierkonstruktors abgelehnt.

In beiden Fällen generiert Visual Studio weiterhin automatisch die erforderlichen Funktionen implizit und gibt standardmäßig keine Warnung aus. Seit Visual Studio 2022, Version 17.7, kann C5267 aktiviert werden, um eine Warnung auszustrahlen.

Die Folgen dieser Regeln können auch in Objekthierarchien einfließen. Wenn beispielsweise eine Basisklasse aus irgendeinem Grund keinen Standardkonstruktor aufweist, der von einer abgeleiteten Klasse aufgerufen werden kann , d. h. einem publicprotected Konstruktor, der keine Parameter akzeptiert, kann eine Klasse, die von ihr abgeleitet wird, keinen eigenen Standardkonstruktor generieren.

Diese Regeln können die Implementierung von geraden, benutzerdefinierten Typen und gängigen C++-Idioms erschweren, z. B. durch das Deklarieren des Kopierkonstruktors und des Kopierzuordnungsoperators als nicht kopierbar und nicht definieren.

struct noncopyable
{
  noncopyable() {};

private:
  noncopyable(const noncopyable&);
  noncopyable& operator=(const noncopyable&);
};

Vor C++11 war dieser Codeausschnitt die idiomatische Form nicht kopierbarer Typen. Es gibt allerdings mehrere Probleme:

  • Der Kopierkonstruktor muss privat deklariert werden, um ihn auszublenden, aber da er überhaupt deklariert wird, wird die automatische Generierung des Standardkonstruktors verhindert. Sie müssen den Standardkonstruktor, falls erforderlich, explizit definieren, auch wenn er keine Aktionen ausführt.

  • Auch wenn der explizit definierte Standardkonstruktor nichts bewirkt, betrachtet der Compiler ihn als nichttrivial. Er ist weniger effizient als ein automatisch generierter Standardkonstruktor und verhindert, dass noncopyable ein echter POD-Typ ist.

  • Obwohl der Kopierkonstruktor und der Kopierzuweisungsoperator vom äußeren Code ausgeblendet werden, können die Memberfunktionen und Friends von noncopyable sie weiterhin anzeigen und aufrufen. Wenn sie deklariert, aber nicht definiert sind, verursacht der Aufruf einen Linkerfehler.

  • Obwohl dies ein allgemein akzeptierter Idiom ist, ist die Absicht nicht klar, es sei denn, Sie verstehen alle Regeln für die automatische Generierung der speziellen Memberfunktionen.

In C++11 kann das nicht kopierbare Idiom auf eine einfachere Weise implementiert werden.

struct noncopyable
{
  noncopyable() =default;
  noncopyable(const noncopyable&) =delete;
  noncopyable& operator=(const noncopyable&) =delete;
};

Beachten Sie, wie die Probleme mit dem Idiom vor Version C++11 gelöst werden:

  • Die Generierung des Standardkonstruktors wird weiterhin verhindert, indem ein Kopierkonstruktor deklariert wird, sie kann jedoch durch explizite Festlegung auf den Standardwert wieder aktiviert werden.

  • Explizit standardmäßige spezielle Memberfunktionen werden immer noch als trivial angesehen, daher gibt es keine Leistungseinbußen und noncopyable wird nicht daran gehindert, ein wahrer POD-Typ zu sein.

  • Der Kopierkonstruktor und der Kopierzuweisungsoperator sind öffentlich, jedoch gelöscht. Es ist ein Kompilierungszeitfehler, um eine gelöschte Funktion zu definieren oder aufzurufen.

  • Der Zweck ist für jeden nachvollziehbar, der mit =default und =delete vertraut ist. Sie müssen die Regeln für die automatische Generierung spezieller Memberfunktionen nicht verstehen.

Ähnliche Idiome sind zum Erstellen benutzerdefinierter Typen vorhanden, die nichtovierbar sind, die nur dynamisch zugeordnet werden können oder die nicht dynamisch zugeordnet werden können. Jedes dieser Idiome verfügt über Implementierungen aus Vorversionen von C++11, bei denen ähnliche Probleme auftreten und die in C++11 auf ähnliche Weise gelöst werden, indem sie in Bezug auf spezielle auf den Standardwert festgelegte und gelöschte Memberfunktionen implementiert werden.

Explizit auf den Standardwert festgelegte Funktionen

Sie können standardmäßig jede der speziellen Memberfunktionen angeben, um explizit anzugeben, dass die spezielle Memberfunktion die Standardimplementierung verwendet, um die spezielle Memberfunktion mit einem nichtublic-Zugriffsqualifizierer zu definieren oder eine spezielle Memberfunktion erneut einzuordnen, deren automatische Generierung durch andere Umstände verhindert wurde.

Sie legen eine spezielle Memberfunktion auf den Standardwert fest, indem Sie sie wie in diesem Beispiel deklarieren:

struct widget
{
  widget()=default;

  inline widget& operator=(const widget&);
};

inline widget& widget::operator=(const widget&) =default;

Beachten Sie, dass Sie eine spezielle Memberfunktion außerhalb des Textkörpers einer Klasse standardmäßig verwenden können, solange sie nicht auflinbar ist.

Aufgrund der Leistungsvorteile von trivialen speziellen Memberfunktionen wird empfohlen, automatisch generierte spezielle Memberfunktionen leeren Funktionsrümpfen vorzuziehen, wenn Sie das Standardverhalten verwenden möchten. Hierzu können Sie entweder die spezielle Memberfunktion explizit auf den Standardwert festlegen oder sie nicht deklarieren (und indem Sie auch keine anderen speziellen Memberfunktionen deklarieren, die deren automatische Generierung verhindern würden).

Deleted-Funktionen

Sie können spezielle Memberfunktionen und normale Memberfunktionen und nichtemberische Funktionen löschen, um zu verhindern, dass sie definiert oder aufgerufen werden. Das Löschen spezieller Memberfunktionen bietet eine sauber möglichkeit, den Compiler daran zu hindern, spezielle Memberfunktionen zu generieren, die Sie nicht benötigen. Die Funktion muss gelöscht werden, wie sie deklariert wird; sie kann danach nicht gelöscht werden, sodass eine Funktion deklariert und später standardmäßig deklariert werden kann.

struct widget
{
  // deleted operator new prevents widget from being dynamically allocated.
  void* operator new(std::size_t) = delete;
};

Das Löschen normaler Memberfunktionen oder Nichtmemberfunktionen verhindert, dass problematische Typenhervoraktionen dazu führen, dass eine unbeabsichtigte Funktion aufgerufen wird. Dies funktioniert, weil gelöschte Funktionen weiterhin an der Überladungsauflösung beteiligt sind und eine bessere Übereinstimmung als die Funktion bereitstellen, die aufgerufen werden könnte, nachdem die Typen erweitert wurden. Der Funktionsaufruf wird in die spezifischere, aber gelöschte Funktion aufgelöst und verursacht einen Compilerfehler.

// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }

Beachten Sie im vorherigen Beispiel, dass das Aufrufen mit einem Argument zu einem float Compilerfehler führen würde, aber das Aufrufen call_with_true_double_onlycall_with_true_double_only mithilfe eines int Arguments würde nicht erfolgen. In dem int Fall wird das Argument von int der double Funktion in die double Version der Funktion heraufgestuft und erfolgreich aufgerufen, obwohl dies möglicherweise nicht beabsichtigt ist. Um sicherzustellen, dass jeder Aufruf dieser Funktion mithilfe eines nicht doppelten Arguments einen Compilerfehler verursacht, können Sie eine Vorlagenversion der gelöschten Funktion deklarieren.

template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.

void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.