Erweiterungsmethoden in C ++

Ursprünglicher Autor: Marius Bancila
  • Übersetzung
  • Tutorial
Vor einigen Tagen veröffentlichte Björn Straustrup dem C ++ - Standardisierungsausschuss den Vorschlag N4174 mit dem Titel " Aufrufsyntax : xf (y) vs. f (x, y) ". Hier ist eine kurze Zusammenfassung seines Wesens: Den Ausdruck xf (y) (ein Aufruf für das Objekt x der Methode f mit dem Argument y ) äquivalent zum Ausdruck f (x, y) (den Aufruf der Funktion f mit den Argumenten x und y ) zu deklarieren . Das heißt

xf (y) bedeutet:
  1. Versuchen Sie, xf (y) aufzurufen: Wenn die Klasse des Objekts x die Methode f enthält, die das Argument y annehmen kann, verwenden Sie diese Methode.
  2. Wenn Punkt 1 fehlschlägt, prüfen wir, ob eine Funktion f existiert, die die Argumente x und y annehmen kann. Wenn ja, benutze es.
  3. Wird weder der eine noch der andere gefunden, erzeugen wir einen Fehler.

f (x, y) bedeutet genau dasselbe:
  1. Versuchen Sie, xf (y) aufzurufen: Wenn die Klasse des Objekts x die Methode f enthält, die das Argument y annehmen kann, verwenden Sie diese Methode.
  2. Wenn Punkt 1 fehlschlägt, prüfen wir, ob eine Funktion f existiert, die die Argumente x und y annehmen kann. Wenn ja, benutze es.
  3. Wird weder der eine noch der andere gefunden, erzeugen wir einen Fehler.

Auf diese Weise haben wir die Möglichkeit, Erweiterungsmethoden zu schreiben, von denen viele C ++ - Programmierer geträumt haben. Ich halte diesen Vorschlag für einen der wichtigsten in der Entwicklung der C ++ - Sprache.

Erweiterungsmethoden in C #


Um besser zu verstehen, wovon wir sprechen, wollen wir uns erinnern, wie Erweiterungsmethoden in C # implementiert sind.

Mit der Erweiterungsmethode können Sie einem vorhandenen Typ Funktionen hinzufügen, ohne den ursprünglichen Typ zu ändern oder einen geerbten Typ zu erstellen (und ohne ein Modul neu kompilieren zu müssen, das den ursprünglichen Typ enthält). Angenommen, Sie möchten einer Zeichenfolgenklasse eine Methode hinzufügen, die die Anzahl der darin enthaltenen Wörter zählt. Dazu können Sie eine WordCount-Methode schreiben, die so aussieht (der Einfachheit halber betrachten wir das Worttrennzeichen nur als Leerzeichen):

static class StringUtilities
{
   public static int WordCount(this string text)
   {
      return text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
   }
}


Jetzt kannst du es so benutzen:

var text = "This is an example";
var count = text.WordCount();

Die Entsprechung von WordCount (Text) und text.WordCount () ist genau das, was Straustrup in Dokument N4174 sagt.

Beachten Sie, dass Erweiterungsmethoden in C # verschiedene Einschränkungen aufweisen:

  • Eine Erweiterungsmethode sollte immer als öffentliche statische Methode einer statischen Klasse deklariert werden
  • Die Erweiterungsmethode hat nur Zugriff auf öffentliche Methoden und Eigenschaften des erweiterbaren Typs


Erweiterungsmethoden in C ++


Eine Frage, die sich jemand stellen könnte, lautet: „Was sind die Vorteile der Äquivalenz von xf (y) und f (x, y) für eine Sprache?“ Die einfache Antwort lautet: Sie ermöglicht es, Erweiterungsmethoden zu definieren und zu verwenden, ohne den vorhandenen Code zu ändern.

Sehen wir uns ein echtes Beispiel an. Standardcontainer in C ++ bieten die find () -Methode zum Suchen eines bestimmten Elements. Die find () -Methode gibt jedoch einen Iterator zurück, und Sie müssen nach end () für Gleichheit suchen, um zu verstehen, ob das Element gefunden wurde oder nicht. Gleichzeitig müssen wir oft nicht das Element selbst finden, sondern prüfen, ob es im Container enthalten ist oder nicht. In Standardcontainern gibt es keine contain () -Methode, aber wir können diese Funktion schreiben:

template
bool contains(std::map const & c, TKey const key)
{
   return c.find(key) != c.end();
}


Und nenne es so:

auto m = std::map {{1, 'a'}, {2, 'b'}, {3,'c'}};
if(contains(m, 1))
{
   std::cout << "key exists" << std::endl;
}


Aber eigentlich wäre es in der Welt der objektorientierten Programmierung schön zu schreiben:

if(m.contains(1))
{
}


In dem Fall, in dem xf (y) und f (x, y) äquivalent sind, ist der obige Code absolut gültig (und schön).

Hier ist ein zweites Beispiel. Angenommen, Sie möchten einige Operatoren definieren, die denen in LINQ unter .NET ähnlich sind. Hier ist eine (vereinfachte) Beispielimplementierung einiger dieser Operatoren für std :: vector.

template
std::vector where(std::vector const & c, UnaryPredicate predicate)
{
   std::vector v;
   std::copy_if(std::begin(c), std::end(c), std::back_inserter(v), predicate);
   return v;
}
template ::type>
std::vector select(std::vector const & c, F s)
{
   std::vector v;
   std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
   return v;
}
template
T sum(std::vector const & c)
{
   return std::accumulate(std::begin(c), std::end(c), 0);
}


Jetzt können wir ein Problem wie das folgende lösen:

auto v = std::vector {1,2,3,4,5,6,7,8,9};
auto t1 = where(v, [](int e){return e % 2 == 0; });
auto t2 = select(t1, [](int e){return e*e; });
auto s = sum(t2);


Ich mag den obigen Code nicht, weil er viele Zwischenvariablen erzeugt, die nur für die Übergabe an den nächsten Aufruf benötigt werden. Wir können versuchen, sie loszuwerden:
auto s = sum(select(where(v, [](int e){return e % 2 == 0; }), [](int e){return e*e; }));


Aber ich mag diesen Code noch weniger. Erstens ist es schwer zu lesen (zu viele Operationen in einer Zeile und sogar andere Formatierungen helfen nicht viel). Zweitens sehen wir die Operationen in umgekehrter Reihenfolge in Bezug darauf, wie sie ausgeführt werden: Zuerst sehen wir den Summenaufruf, dann die Auswahl und erst dann, wo. Zu verstehen, wo die Argumente einer Funktion enden und die Argumente der zweiten beginnen, ist ebenfalls nicht sehr praktisch.

Wenn der Sprachstandard jedoch die Äquivalenz von xf (y) und f (x, y) bestimmt, ist es sehr einfach, diesen Code zu schreiben:
auto v = std::vector {1,2,3,4,5,6,7,8,9};
auto s = v.where([](int e){return e % 2 == 0; })
          .select([](int e){return e*e; })
          .sum();


Wirklich wunderschön? Ich denke schon

Fazit


Das Dokument N4174 ähnelt bislang eher einer Untersuchung theoretischer Möglichkeiten als einer formalen Norm. Es gibt viele verschiedene Aspekte, die sorgfältig geprüft werden müssen. Wenn Sie interessiert sind, lesen Sie das Dokument selbst. Trotzdem sieht das Feature unbestreitbar nützlich aus und ich hoffe, dass der Tag kommt, an dem es in die Standardsprache übergeht.

Jetzt auch beliebt: