Forum: PC-Programmierung C++ Referenz oder Pointer?


von Thomas (Gast)


Lesenswert?

Hallo,
bei der Programmierung in C++ besteht ja die Möglichkeit, 
Funktionsparameter per Referenz zu übergeben.
Von der Lesbarkeit finde ich dieses ja besser. Mich würde nur mal 
interessieren,  welche Version effizienter umgesetzt wird.
Auf einem AVR könnte ich mir ja den Assemblertext ansehen und 
vergleichen, aber auf dem PC steig ich da nicht so durch.

Macht der Kompilier aus beiden Versionen der Funktion den gleichen 
Assemblercode?

Beispiel:
1
void tauschReferenz(int &a, int &b)
2
{
3
    int help;
4
    help = a;
5
    a = b;
6
    b = help;
7
}
8
9
void tauschPointer(int *a, int *b)
10
{
11
    int help;
12
    help = *a;
13
    *a = *b;
14
    *b = help;
15
}

Gruß
Thomas

von Uhu U. (uhu)


Lesenswert?

Der Maschinencode sollte sich nicht unterscheiden, da der Compiler 
hinter den Kulissen natürlich Pointer auf Referenzparameter übergibt.

von Thomas (Gast)


Lesenswert?

Ich habe ein C++ Buch in dem als Beispiel Objekte "Datum" vorhanden 
sind.
Für diese Klasse gibt es dann eine Funktion exchange, die die Inhalte 
zweier Objekte tauscht.
In der Beschreibung steht, dass "in diesem Fall mit Zeigern gearbeitet 
werden sollte weil beim Aufruf der Funktion dann die Adresse des Objekts 
angegeben werden muss".
Warum man das sollte steht da leider nicht.

Hier die Methode:
1
void Datum::exchange(Datum *pd)
2
{
3
    Datum temp = *pd;
4
    *pd = *this;
5
    *this = temp;
6
}

Vom Prinzip her ist das dann doch ähnlich meiner beiden "tausch" 
Funktionen oben, oder?

von Rolf Magnus (Gast)


Lesenswert?

Manche C++-Programmierer finden es besser, Parameterwerte, die durch die 
Funktion verändert werden, per Zeiger zu übergeben statt per Referenz, 
da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß 
sie den Wert verändert.

von Thomas B. (yahp) Benutzerseite


Lesenswert?

Der schöne Vorteil bei Referenzen ist, dass man innerhalb der Funktion 
nicht ständig dereferenzieren muss.


> Manche C++-Programmierer finden es besser, Parameterwerte, die durch die
> Funktion verändert werden, per Zeiger zu übergeben statt per Referenz,
> da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß
> sie den Wert verändert.

Parameter gehören ja ohnehin dokumentiert, um es nicht dem Rätselraten 
zu überlassen, ob sich was ändert oder nicht. Per Pointer übergebene 
Sachen könnten sich ja beispielsweise auch nicht ändern.

von Peter (Gast)


Lesenswert?

nimm die pointer methode. ist erstens lesbarer und zweitens kannst du 
den code auch in einem c programm verwenden.

von Bartli (Gast)


Lesenswert?

> Manche C++-Programmierer finden es besser, Parameterwerte, die durch die
> Funktion verändert werden, per Zeiger zu übergeben statt per Referenz,
> da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß
> sie den Wert verändert.

> Parameter gehören ja ohnehin dokumentiert, um es nicht dem Rätselraten
> zu überlassen, ob sich was ändert oder nicht. Per Pointer übergebene
> Sachen könnten sich ja beispielsweise auch nicht ändern.

Najaaa....da halt ich mich also lieber an die Funktionsprototypen*:

void foo(const std::string & s);
void bar(std::string & s);

Bei foo kann s nicht verändert werden (ausser der Halbschuh der foo 
programmiert hat casted irgendwo die Konstantheit weg), bei bar kann s 
verändert werden, da s nicht als konstante Referenz deklariert ist (und 
soll vermutlich verändert werden, weil sonst wär s konstant...)

*) Und sonst les ich den Code wenn ich befürchte dass etwas nicht 
stimmt. Die Dokumentation lügt sowieso bzw. ist veraltet bzw. ist nicht 
vorhanden :D

von Uhu U. (uhu)


Lesenswert?

So seh ich das auch - zumal die richtig tollen IDEs die 
Funktionsprototypen als Tooltip anzeigen.

von Karl H. (kbuchegg)


Lesenswert?

Zunächst mal muss unterschieden werden:
Soll die Funktion den Parameter beim Aufrufer ändern können
oder nicht?

Falls sie nicht ändern können soll:

   Handelt es sich beim Argument um einen int, char, bool, double
   oder einen sonstigen sog. eingebauten Datentyp?

   wenn ja:  übergib das Argument direkt
             bsp:  void foo( int i );

   wenn nein: übergib eine konstante Referenz
             bsp:  void foo( const std::string& i );

Falls die Funktion das Argument beim Aufrufer ändern können soll:

   Übergib eine Referenz
   bsp:   void foo( int& i );
          void foo( std::string& i );



In C++ werden Pointer wesentlich seltener eingesetzt als in C.
Stattdessen nimmt man lieber Referenzen.

(Die Unterscheidung im Fall 1 zwischen eingebauten Datentypen
und Klassen beruht darauf, dass eine Kopie eines int zu erzeugen
eine billige Operation ist, während das Erzeugen einer Kopie
eines Klassenobjektes normalerweise eine teure Operation ist)

von Karl H. (kbuchegg)


Lesenswert?

> In der Beschreibung steht, dass "in diesem Fall mit Zeigern gearbeitet
> werden sollte weil beim Aufruf der Funktion dann die Adresse des Objekts
> angegeben werden muss".
> Warum man das sollte steht da leider nicht.

Es ist auch Unsinn. Der Autor hat den Schritt von C zu C++
noch nicht wirklich konsequent vollzogen.

void Datum::exchange( Datum& With )
{
    Datum temp = With;
    With = *this;
    *this = temp;
}

von Karl H. (kbuchegg)


Lesenswert?

Rolf Magnus wrote:
> Manche C++-Programmierer finden es besser, Parameterwerte, die durch die
> Funktion verändert werden, per Zeiger zu übergeben statt per Referenz,
> da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß
> sie den Wert verändert.

Dieses Argument kommt immer wieder. Ich halte es allerdings
für ein NULL-Argument.

Klar. Bei Beispielen die Funktionsnamen wie foo() oder bar()
verwenden, stimmt das tatsächlich.
In der Praxis hat man aber vernünftige Funktions und Argumentnamen
und bei denen stellt sich diese Fragestellung normalerweise nicht
bzw. nur sehr selten.

von Toll.. wirklich super. (Gast)


Lesenswert?

Und wie soll so ein Name heißen, der darauf hinweißt, dass der 
Vatriableninhalt verändert wird?
pArgument gibts ja net mehr - is ja kein Pointer.

Die Praxis... jaa.. ich sehs überall. Jegliche APIs verwenden 
Referenzen... ohne Ende. ;-)

Ich verwende Referenzen nur, wenn der Variableninhalt von der Funktion 
nicht verändert wird. Da dies bei dieser Art der Parameterübergabe 
schlichtweg nicht klenntlich gemacht werden kann, ob eine veränderung 
des Inhalts ansteht, verschlechtert sich nur die Klarheit im Code.
Der Sinn der Referenzen an dieser stelle ist der schnellere Aufruf der 
Funktion. Große Strukturen oder Klassen ersparen sich damit den Aufruf 
des Copy-Ctors und pumpen den Stack nicht sinnlos zu.

MFG

von Thomas (Gast)


Lesenswert?

Erstmal Danke für die Infos.
So wie es scheint, ist es mehr oder weniger Geschmackssache ob ich nun 
einen Pointer oder eine Referenz verwende.

Obwohl ich sagen muss, dass ich das Argument einen Pointer zu verwenden, 
um kenntlich zu machen dass der Wert verändert wird, gar nicht mal 
schlecht finde.
Allerdings habe ich bis jetzt bei meinen Referenzen die nicht geändert 
werden mit einem "const" gekennzeichnet. Dies ist im Gegensatz zum 
Pointer beim Funktionsaufruf aber nicht mehr sichtbar.

Gibt es denn überhaupt einen funktionalen Unterschied? Gut, 
Pointerarithmetik wird es wohl nicht mehr geben ;-)

von Karl H. (kbuchegg)


Lesenswert?

Toll.. wirklich super. wrote:
> Und wie soll so ein Name heißen, der darauf hinweißt, dass der
> Vatriableninhalt verändert wird?
> pArgument gibts ja net mehr - is ja kein Pointer.

Ich spreche nicht von ungarischer Notation.

Wenn du eine Funktion aufrufst, die 2 Werte vertauscht:
  swap( Wert1, Wert2 );

werden dann die Argumente verändert?
Na sicher doch, das ist ja der Sinn der Funktion.

Wenn du einen Aufruf siehst:
   NettoBetrag = CalcNetto( BruttoBetrag, Prozent );
wird da in CalcNetto eines der beiden Argumente verändert?
Sicher nicht. Das wäre wohl ziemlich kontraproduktiv und
auch nicht Aufgabe der Funktion. Die Funktion soll einen
Nettobetrag ausrechnen, drumm heist sie auch CalcNetto.
Dazu muss keines der Argumente verändert werden.

Was ist mit
   Withdraw( UserAccount, Amount );
welches der beiden Argumente wird wohl von der Funktion
verändert werden?
genau: UserAccount wird sich verändern, Amount wird sich nicht
verändern.

Funktionen sollen das tun was aussen drauf steht (wie sie heissen).
Nicht mehr und nicht weniger. Und daraus ergibt sich automatisch
welche Argumente verändert werden können und welche nicht.

> Die Praxis... jaa.. ich sehs überall. Jegliche APIs verwenden
> Referenzen... ohne Ende. ;-)

APIs muessen viele Programmiersprachen unterstützen. Da muss
man den kleinsten gemeinsamen Nenner nehmen. Und der ist nun
mal: Eine Adresse wird übergeben.

von Karl H. (kbuchegg)


Lesenswert?

Thomas wrote:

> Gibt es denn überhaupt einen funktionalen Unterschied? Gut,
> Pointerarithmetik wird es wohl nicht mehr geben ;-)

Kannst du genauso machen.
Eine Referenz ist ja nichts anderes als ein 'neuer Name
für ein ansonsten existierendes Objekt'. Von dem Objekt
(über die Referenz) kann man die Adresse bestimmen und damit
Pointerarithmetik machen.

Aber es gibt tatsächlich einen qualitativen Unterschied.
Wenn deine Schnittstelle die Möglichkeit haben muss, den
Sachverhalt 'kein Objekt' auszudrücken, dann kommst du um
einen Pointer nicht drumherum. Das geht nicht mit Referenzen.

von Rolf Magnus (Gast)


Lesenswert?

> Von dem Objekt (über die Referenz) kann man die Adresse bestimmen und
> damit Pointerarithmetik machen.

Auf sowas wird man aber in der Regel tunlichst verzichten. Wenn eine 
Funktion eine Referenz auf ein Objekt will, würde ich nicht annehmen, 
daß sie das als Element eines Arrays ansieht und einen Zeiger bildet, um 
darüber auf die anderen Elemente zuzugreifen.

von Εrnst B. (ernst)


Lesenswert?

Thomas wrote:
> Beispiel:
1
void tauschReferenz(int &a, int &b)
2
void tauschPointer(int *a, int *b)

Eigentlich nimmt man keines von beiden, sondern die 
swap-Templatefunktion aus der C++ Standardbibliothek.
1
#include <algorithm>
2
...
3
4
int a;
5
int b;
6
std::swap(a,b);
7
8
std::string sa;
9
std::string sb;
10
std::swap(sa,sb);
11
12
// Und dank template-Spezialisierung
13
std::list<Irgendwas> la;
14
std::list<Irgendwas> lb;
15
std::swap(la,lb);

Beachte: bei deiner Implementierung würden beim letzten swap die (evtl 
mehrere MB grossen) Listen dreimal umkopiert werden, mit std::swap 
werden nur ein paar interne Pointer in den Listen angepasst (ruft intern 
std::list::swap auf).

/Ernst

von Peter (Gast)


Lesenswert?

> Eigentlich nimmt man keines von beiden, sondern die
> swap-Templatefunktion aus der C++ Standardbibliothek.
...und man benutzt
1
using namespace std;
 damit man nicht 1000 mal std:: hinschreiben muss ;-)

von Bartli (Gast)


Lesenswert?

Peter, du enttäuschst mich. Von dir hätte ich jetzt den Kommentar 
erwartet, dass man Namespaces überhaupt nicht brauchen tut, weils

1. Lesbarer ist und
2. Man dann den Code auch durch den C Compiler jagen kann

von Rolf Magnus (Gast)


Lesenswert?

> ...und man benutzt
> using namespace std;
> damit man nicht 1000 mal std:: hinschreiben muss ;-)

Am besten in jedem Header, um möglichst viele Namenskonflikte zu 
provizieren so das Konzept von Namespaces nutzlos (useless namespace 
std) zu machen.

von Peter (Gast)


Lesenswert?

namenskonflikte sind nicht schlimm, die meldet der compiler sofort und 
dann kann man es immer noch ändern. sinnlose schreibarbeit sparen wäre 
mir wichtiger.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.