Ich bin auf dieses seltsame Code-Snippet gestoßen, das sich gut kompilieren lässt:
class Car
{
public:
int speed;
};
int main()
{
int Car::*pSpeed = &Car::speed;
return 0;
}
Warum hat C ++ diesen Zeiger auf ein nicht statisches Datenelement einer Klasse? Was nützt dieser seltsame Zeiger in echtem Code?
Antworten:
Es ist ein "Zeiger auf Mitglied" - der folgende Code veranschaulicht seine Verwendung:
Was , warum würden Sie wollen, das zu tun, auch sie gibt Ihnen eine weitere Dereferenzierungsebene , die einige knifflige Probleme lösen kann. Aber um ehrlich zu sein, musste ich sie nie in meinem eigenen Code verwenden.
Bearbeiten: Ich kann mir keine überzeugende Verwendung für Zeiger auf Mitgliedsdaten vorstellen. Zeiger auf Mitgliedsfunktionen können in steckbaren Architekturen verwendet werden, aber das Erstellen eines Beispiels auf kleinem Raum besiegt mich erneut. Das Folgende ist mein bester (ungetesteter) Versuch - eine Apply-Funktion, die einige Vor- und Nachbearbeitungen durchführt, bevor eine vom Benutzer ausgewählte Member-Funktion auf ein Objekt angewendet wird:
Die Klammern
c->*func
sind erforderlich, da der->*
Operator eine niedrigere Priorität hat als der Funktionsaufrufoperator.quelle
Dies ist das einfachste Beispiel, das ich mir vorstellen kann und das die seltenen Fälle vermittelt, in denen diese Funktion relevant ist:
Hier ist der an count_fruit übergebene Zeiger zu beachten. Dies erspart Ihnen das Schreiben separater Funktionen count_apples und count_oranges.
quelle
&bowls.apples
und&bowls.oranges
?&bowl::apples
und&bowl::oranges
zeigt auf nichts.&bowl::apples
und&bowl::oranges
zeigen Sie nicht auf Mitglieder eines Objekts ; Sie zeigen auf Mitglieder einer Klasse . Sie müssen mit einem Zeiger auf ein tatsächliches Objekt kombiniert werden, bevor sie auf etwas zeigen. Diese Kombination wird mit dem->*
Bediener erreicht.Eine weitere Anwendung sind aufdringliche Listen. Der Elementtyp kann der Liste mitteilen, wie die nächsten / vorherigen Zeiger lauten. Die Liste verwendet also keine fest codierten Namen, kann jedoch vorhandene Zeiger verwenden:
quelle
next
.Hier ist ein reales Beispiel aus Signalverarbeitungs- / Steuerungssystemen, an dem ich gerade arbeite:
Angenommen, Sie haben eine Struktur, die die Daten darstellt, die Sie sammeln:
Angenommen, Sie füllen sie in einen Vektor:
Angenommen, Sie möchten eine Funktion (z. B. den Mittelwert) einer der Variablen über einen Bereich von Stichproben berechnen und diese Mittelwertberechnung in eine Funktion einbeziehen. Der Zeiger auf das Mitglied macht es einfach:
Hinweis Bearbeitet am 05.08.2016 für einen präziseren Ansatz mit Vorlagenfunktionen
Und natürlich können Sie eine Vorlage erstellen, um einen Mittelwert für jeden Forward-Iterator und jeden Werttyp zu berechnen, der die Addition mit sich selbst und die Division durch size_t unterstützt:
BEARBEITEN - Der obige Code hat Auswirkungen auf die Leistung
Wie ich bald herausfand, sollten Sie beachten, dass der obige Code einige schwerwiegende Auswirkungen auf die Leistung hat. Die Zusammenfassung lautet: Wenn Sie eine Zusammenfassungsstatistik für eine Zeitreihe oder eine FFT usw. berechnen, sollten Sie die Werte für jede Variable zusammenhängend im Speicher speichern. Andernfalls führt das Durchlaufen der Serie zu einem Cache-Fehler für jeden abgerufenen Wert.
Betrachten Sie die Leistung dieses Codes:
Auf vielen Architekturen eine Instanz von
Sample
füllt eine eine Cache-Zeile. Bei jeder Iteration der Schleife wird ein Sample aus dem Speicher in den Cache gezogen. 4 Bytes von der Cache-Zeile werden verwendet und der Rest wird weggeworfen, und die nächste Iteration führt zu einem weiteren Cache-Fehler, Speicherzugriff und so weiter.Viel besser, um dies zu tun:
Wenn nun der erste x-Wert aus dem Speicher geladen wird, werden auch die nächsten drei in den Cache geladen (vorausgesetzt, die Ausrichtung ist angemessen), sodass für die nächsten drei Iterationen keine Werte geladen werden müssen.
Der obige Algorithmus kann durch die Verwendung von SIMD-Anweisungen auf z. B. SSE2-Architekturen etwas weiter verbessert werden. Diese funktionieren jedoch viel besser, wenn alle Werte im Speicher zusammenhängend sind und Sie mit einem einzigen Befehl vier Samples zusammen laden können (mehr in späteren SSE-Versionen).
YMMV - Entwerfen Sie Ihre Datenstrukturen entsprechend Ihrem Algorithmus.
quelle
double Sample::*
Teil ist der Schlüssel!Sie können später dieses Mitglied Zugriff auf jeden Fall:
Beachten Sie, dass Sie eine Instanz benötigen, um sie aufzurufen, damit sie nicht wie ein Delegat funktioniert.
Es wird selten verwendet, ich habe es in all meinen Jahren vielleicht ein- oder zweimal gebraucht.
Normalerweise ist die Verwendung einer Schnittstelle (dh einer reinen Basisklasse in C ++) die bessere Wahl für das Design.
quelle
IBM verfügt über weitere Dokumentationen zur Verwendung. Kurz gesagt, Sie verwenden den Zeiger als Versatz in der Klasse. Sie können diese Zeiger nicht außer der Klasse verwenden, auf die sie sich beziehen.
Es scheint ein wenig dunkel zu sein, aber eine mögliche Anwendung ist, wenn Sie versuchen, Code zum Deserialisieren generischer Daten in viele verschiedene Objekttypen zu schreiben, und Ihr Code Objekttypen verarbeiten muss, von denen er absolut nichts weiß (z. B. Ihr Code ist in einer Bibliothek, und die Objekte, in die Sie deserialisieren, wurden von einem Benutzer Ihrer Bibliothek erstellt). Die Elementzeiger bieten Ihnen eine allgemeine, halb lesbare Möglichkeit, auf die einzelnen Datenelement-Offsets zu verweisen, ohne auf typenlose void * -Tricks zurückgreifen zu müssen, wie Sie es für C-Strukturen tun könnten.
quelle
Es ermöglicht die einheitliche Bindung von Mitgliedsvariablen und -funktionen. Das Folgende ist ein Beispiel für Ihre Fahrzeugklasse. Eine häufigere Verwendung wäre verbindlich
std::pair::first
und::second
bei Verwendung in STL-Algorithmen und Boost auf einer Karte.quelle
Sie können ein Array von Zeigern auf (homogene) Mitgliedsdaten verwenden, um eine doppelte Schnittstelle mit benannten Mitgliedern (iexdata) und Array-tiefgestellten (dh x [idx]) zu aktivieren.
quelle
union
Wortspiels auf diese Weise ist vom Standard nicht zulässig, da es zahlreiche Formen undefinierten Verhaltens hervorruft ... während diese Antwort in Ordnung ist.float *component[] = { &x, &y, &z }; return *component[idx];
heißt, der Zeiger auf Komponente scheint keinen anderen Zweck als die Verschleierung zu erfüllen.Eine Möglichkeit, die ich verwendet habe, besteht darin, zwei Implementierungen zu haben, wie etwas in einer Klasse ausgeführt werden soll, und ich möchte zur Laufzeit eine auswählen, ohne ständig eine if-Anweisung durchlaufen zu müssen, d. H.
Offensichtlich ist dies nur dann praktisch nützlich, wenn Sie das Gefühl haben, dass der Code so stark gehämmert wird, dass die if-Anweisung die Ausführung verlangsamt, z. tief im Bauch eines intensiven Algorithmus irgendwo. Ich denke immer noch, dass es eleganter ist als die if-Aussage, selbst in Situationen, in denen es keinen praktischen Nutzen hat, aber das ist nur meine Meinung.
quelle
Algorithm
und zwei abgeleiteten Klassen erreichen, z . B.AlgorithmA
undAlgorithmB
. In einem solchen Fall sind beide Algorithmen gut voneinander getrennt und müssen unabhängig voneinander getestet werden.Zeiger auf Klassen sind keine echten Zeiger; Eine Klasse ist ein logisches Konstrukt und hat keine physische Existenz im Speicher. Wenn Sie jedoch einen Zeiger auf ein Mitglied einer Klasse erstellen, wird ein Objekt in der Klasse des Mitglieds versetzt, in dem sich das Mitglied befindet. Dies gibt eine wichtige Schlussfolgerung: Da statische Elemente keinem Objekt zugeordnet sind, kann ein Zeiger auf ein Element NICHT auf ein statisches Element (Daten oder Funktionen) verweisen. Beachten Sie Folgendes:
Quelle: Die vollständige Referenz C ++ - Herbert Schildt 4. Auflage
quelle
Ich denke, Sie möchten dies nur tun, wenn die Mitgliedsdaten ziemlich groß waren (z. B. ein Objekt einer anderen ziemlich umfangreichen Klasse) und Sie eine externe Routine haben, die nur mit Verweisen auf Objekte dieser Klasse funktioniert. Sie möchten das Mitgliedsobjekt nicht kopieren, damit Sie es weitergeben können.
quelle
Hier ist ein Beispiel, in dem ein Zeiger auf Datenelemente nützlich sein könnte:
quelle
Angenommen, Sie haben eine Struktur. Innerhalb dieser Struktur befinden sich * irgendeine Art von Name * zwei Variablen des gleichen Typs, aber mit unterschiedlicher Bedeutung
Okay, jetzt nehmen wir an, Sie haben ein paar
foo
s in einem Container:Angenommen, Sie laden die Daten aus separaten Quellen, aber die Daten werden auf dieselbe Weise dargestellt (z. B. benötigen Sie dieselbe Analysemethode).
Sie könnten so etwas tun:
Zu diesem Zeitpunkt gibt der Aufruf
readValues()
einen Container mit einer Übereinstimmung von "input-a" und "input-b" zurück. Alle Schlüssel sind vorhanden, und Foos mit haben entweder a oder b oder beides.quelle
Um einige Anwendungsfälle für die Antwort von @ anon & @ Oktalist hinzuzufügen, finden Sie hier ein großartiges Lesematerial über die Zeiger-zu-Mitglied-Funktion und die Zeiger-zu-Mitglied-Daten.
https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-ptmf4.pdf
quelle