Forum: PC-Programmierung C++ - Für was sind "const" funktionen?


von Forest D. (thecomet)


Lesenswert?

Hallo zusammen. Kann mir mal jemand sagen wofür dieses "const" ist vor 
der Funktion?
1
const vec3 operator+( vec3 &e )

Und was ist der unterschied zwischen dem und wenn ich es erst am schluss 
schreibe?
1
vec3 operator+( vec3 &e ) const

Kann man auch "const" weglassen?

Hier das Ganze:
1
// Overloading Operators
2
// by Forest DUMP
3
4
#include <iostream>
5
#include <iomanip>
6
7
using namespace std;
8
9
// ---------------------------------------------------------------------------------
10
// vector 3 class
11
class vec3
12
{
13
    public:
14
15
        // vector 3 elements
16
        float x;
17
        float y;
18
        float z;
19
20
        // overload operators
21
        const vec3 operator+( vec3 &e );
22
23
        // constructor, destructor
24
        vec3( void );
25
        vec3( float x, float y, float z );
26
        ~vec3();
27
};
28
29
// ---------------------------------------------------------------------------------
30
// ability to set values on creation in the constructor
31
vec3::vec3(float x, float y, float z)
32
{
33
    this->x = x;
34
    this->y = y;
35
    this->z = z;
36
}
37
38
// ...or not
39
vec3::vec3( void )
40
{
41
}
42
43
// ---------------------------------------------------------------------------------
44
// destructor
45
vec3::~vec3()
46
{
47
}
48
49
// ---------------------------------------------------------------------------------
50
// Overload plus operator
51
const vec3 vec3::operator+( vec3 &e )
52
{
53
    vec3 result;
54
    result.x = x + e.x;
55
    result.y = y + e.y;
56
    result.z = z + e.z;
57
    return result;
58
}
59
60
// ---------------------------------------------------------------------------------
61
// main entry point (I think DarkGDK has some other form of this)
62
int main( int argc , char* argv[] )
63
{
64
65
    // define 2 vector objects
66
    vec3 myVector1(4, 4, 4);
67
    vec3 myVector2(6, 7, 3);
68
69
    // add vectors together and store in result vector
70
    vec3 result;
71
    result = myVector1 + myVector2;
72
73
    // output vectors
74
    cout << "Vector 1 : " << myVector1.x << "," << myVector1.y << "," << myVector1.z << endl;
75
    cout << "Vector 2 : " << myVector2.x << "," << myVector2.y << "," << myVector2.z << endl;
76
    cout << "result : " << result.x << "," << result.y << "," << result.z << endl;
77
78
    // end program
79
    cin.ignore();
80
    return 0;
81
}

von (prx) A. K. (prx)


Lesenswert?

const vorne: bezieht sich auf die retournierten Daten.

const hinten: bezieht sich auf die Funktion. Eine Funktion, die den 
Zustand des Objekts nicht verändert (=> mögliche Optimierung).

von Peter II (Gast)


Lesenswert?

> const vec3 operator+( vec3 &e )

liefer ein vec3 zurück der aber const ist, er darf/kann also nicht 
veändert werden. Macht aber nicht so viel sinn, wird im zusammenhang mit 
referenz verwenden.

z.b.
1
const vec3& operator+( vec3 &e )
damit verhindert man, das jemand ein objekt was man nur als Referenz 
rausgibt ändert.

Und was ist der unterschied zwischen dem und wenn ich es erst am schluss
schreibe?

vec3 operator+( vec3 &e ) const

hier sagt das const, das das objekt von der methode nicht durch den 
methoden aufruf geändert wird. (gibt auch wieder ausnahmen (mutable)

Kann man auch "const" weglassen?
kann man, aber ist sollte man nicht.

von Forest D. (thecomet)


Lesenswert?

Aah, das ergibt jetzt ja viel mehr Sinn. Das heisst also dass das hier 
nicht funktionieren würde?
1
vec3 vec3::operator+( vec3& e ) const
2
{
3
   vec3 result;
4
   this->x = 10;        // Würde nicht funktionieren?
5
   return result;
6
}

Aber ohne das "const" würde das gehen?

von Peter II (Gast)


Lesenswert?

Forest Dump schrieb:
> Das heisst also dass das hier
> nicht funktionieren würde?

wenn x nicht mutable ist - ja.

von Forest D. (thecomet)


Lesenswert?

Super, vielen Dank!

von Forest D. (thecomet)


Lesenswert?

Nochmals eine kleine Frage, was machen diese zwei consts die 
unterstrichen sind?

const int* const Method3(const int* const & ) const;

von (prx) A. K. (prx)


Lesenswert?

Beide gleich: konstante Zeiger. In zweiten Fall als Referenz darauf.

Die Syntax von Typdeklarationen in C und infolgedessen C++ ist 
hoffnungslos verhunzt, da ist nichts mehr zu retten. Daher diese 
Konfusion. Mit Googles Go bin ich erstmals einer in syntaktischer 
Tradition von C stehenden Sprache begegnet, die sich davon etwas ablöst.

von Rolf Magnus (Gast)


Lesenswert?

A. K. schrieb:
> Beide gleich: konstante Zeiger. In zweiten Fall als Referenz darauf.
>
> Die Syntax von Typdeklarationen in C und infolgedessen C++ ist
> hoffnungslos verhunzt, da ist nichts mehr zu retten.

Na komm, so schwer ist das in diesem Fall auch nicht. const bezieht sich 
immer auf das, was direkt links davon steht, mit einer Ausnahme:  Wenn 
links nichts mehr ist, bezieht es sich auf das, was direkt rechts davon 
steht.
Richtig lustig wird's eigentlich erst, wenn man Funktionen deklariert, 
die mit Funktionszeigern arbeitet. Ein gerne genommenes Beispiel ist die 
Standard-Funktion signal(), deren Deklaration so aussieht:
1
void (*signal(int signum, void (*handler)(int)))(int);

Aber auch das läßt sich mit einem Typedef entschärfen:
1
typedef void (*sighandler_t)(int);
2
3
sighandler_t signal(int signum, sighandler_t handler);

> Mit Googles Go bin ich erstmals einer in syntaktischer Tradition von C
> stehenden Sprache begegnet, die sich davon etwas ablöst.

Bei Java hat man das gelöst, indem man einfach die Hälfte weggelassen 
hat.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf Magnus schrieb:
> Na komm, so schwer ist das in diesem Fall auch nicht. const bezieht sich
> immer auf das, was direkt links davon steht, mit einer Ausnahme:  Wenn
> links nichts mehr ist, bezieht es sich auf das, was direkt rechts davon
> steht.

Ich hätte eher gesagt, const bezieht sich in C immer auf das, was direkt
rechts daneben steht, und das ohne Ausnahme, also noch einfacher.

In C++ gibt es noch das const rechts neben einer Funktionsdeklaration.
Da kann diese Regel natürlich nicht gelten, aber hier hat const auch
eine ganz andere Bedeutung, weswegen man es sich getrennt merken muss.
Da wurde einfach nur Schlüsselwortrecycling gemacht, ähnlich wie bei
static, wenn es außerhalb eines Funktionsrumpfs benutzt wird.

Eigentlich ist die Typdeklaration in C und C++ ziemlich logisch und
konsistent, wenn man das Prinzip erst mal kapiert hat. Am besten ist es,
man arbeitet sich von innen, beginnend mit dem deklarierten Variablen-
namen, nach außen.

von Rolf Magnus (Gast)


Lesenswert?

Yalu X. schrieb:
> Rolf Magnus schrieb:
>> Na komm, so schwer ist das in diesem Fall auch nicht. const bezieht sich
>> immer auf das, was direkt links davon steht, mit einer Ausnahme:  Wenn
>> links nichts mehr ist, bezieht es sich auf das, was direkt rechts davon
>> steht.
>
> Ich hätte eher gesagt, const bezieht sich in C immer auf das, was direkt
> rechts daneben steht, und das ohne Ausnahme, also noch einfacher.

Das ist aber falsch.
1
// zweimal ein nichtkonstanter Zeiger auf einen konstanten int
2
int const * p1;
3
const int * p2;
4
5
// ein nichtkonstanter Zeiger auf einen konstanten Zeiger
6
// auf einen konstanten int
7
int const * const * p3;

> In C++ gibt es noch das const rechts neben einer Funktionsdeklaration.
> Da kann diese Regel natürlich nicht gelten, aber hier hat const auch
> eine ganz andere Bedeutung.

Dennoch gilt aber meine Regel auch da: const bezieht sich auf die 
Funktion, also das Mehod3(...), was links davon steht.

von (prx) A. K. (prx)


Lesenswert?

Klar, man kann damit umgehen, aber es bleibt Murks, der sich ergab, weil 
in Deklarationen abgeleitete Typen teils durch ein Symbol links vom 
Namen (pointer) und teils rechts vom Namen (array, function) 
gekennzeichnet werden.

Und sich folglich abgeleitete Typen ineinander schachteln, statt sich 
einfach linear aufzureihen.

Weiter verkompliziert wurde es dadurch, dass Klammern darin zweierlei 
verschiedene Bedeutungen haben, nämlich Funktionen und Prioritäten. Was 
in C++ zur Folge hat, dass
  int(a);
eigentlich gleichermassen Typumwandlung und Deklaration mit unnötiger 
Klammer sein könne.

Für die natürliche Platzierung von const und volatile entsprechend der 
Umgangssprache war schlussendlich kein Platz mehr, so dass die an für 
Anfänger irritierende Stellen wanderten. Ausser anfangs beim strukturell 
ähnlichen _far - das landete da, wo es der Anfänger vermutet aber es von 
der syntaktischen Logik her nicht hingehört.

Das ist schlicht ein Konstruktionsfehler von C.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf Magnus schrieb:
> Das ist aber falsch.

Ich glaube eher, wie haben eine Unterschiedliche Leseweise für
Typdeklarationen.

> // zweimal ein nichtkonstanter Zeiger auf einen konstanten int
> int const * p1;

Mit meiner oben vorgeschlagenen "Von-Innen-Nach-Außen-Methode" (die IMHO
auch Ritchie bei der Konzeptioen der Deklarationssystax im Kopf gehabt
hat) lese ich das so:

p1 dereferenziert ist konstant und vom Typ int.

> const int * p2;

p2 dereferenziert ist vom Typ int und dieser int-Wert ist konstant.

> // ein nichtkonstanter Zeiger auf einen konstanten Zeiger
> // auf einen konstanten int
> int const  const  p3;

p3 dereferenziert ist konstant,
nochmals dereferenziert ist er wieder konstant und vom Typ int.

Diese Leseweise mag nach holprigem Deutsch aussehen, ist aber bei der
systematischen Analyse und Synthese stark verschachtelter Deklarationen
mit durchmischten Pointern, Arrays und Funktionspointern von Vorteil.

>> In C++ gibt es noch das const rechts neben einer Funktionsdeklaration.
>> Da kann diese Regel natürlich nicht gelten, aber hier hat const auch
>> eine ganz andere Bedeutung.
>
> Dennoch gilt aber meine Regel auch da: const bezieht sich auf die
> Funktion, also das Mehod3(...), was links davon steht.

Man könnte auch sagen, das const bezieht sich auf die Membervariablen,
die von der Methode nicht verändert werden. Dadurch, dass rechts vom
const nichts steht, wird angezeigt, dass die konstanten Dinge woanders
stehen, nämlich dort, wor die Membervariablen deklariert sind. Insofern
gilt meine Regel ebenfalls :)

von (prx) A. K. (prx)


Lesenswert?

Nur zur Erinnerung: Die Reihenfolge der Schlüsselworte vor dem "*" ist 
irrelevant. Ob man
  long int extern const *p;
oder
  extern int const long *p;
oder
  const long extern int *p;
schreibt ist schnurzpiepegal. Es landet alles im gleichen Topf.

von Juergen (Gast)


Lesenswert?

Yalu X. schrieb:
>> int const * p1;
>
> Mit meiner oben vorgeschlagenen "Von-Innen-Nach-Außen-Methode" (die IMHO
> auch Ritchie bei der Konzeptioen der Deklarationssystax im Kopf gehabt
> hat) lese ich das so:
>
> p1 dereferenziert ist konstant und vom Typ int.

Das ist falsch, const bezieht sich hier auf int.

>> const int * p2;
>
> p2 dereferenziert ist vom Typ int und dieser int-Wert ist konstant.

Das ist richtig.  Bei

int const * p1;
const int * p2;

haben p1 und p2 den gleichen Typ!

Für einen konstanten Zeiger mußt du

int * const p3;

schreiben.

Manche Leute schreiben die Qualifier auch einheitlich rechts neben den 
Typ, auf den sie sich beziehen.

Jürgen

von (prx) A. K. (prx)


Lesenswert?

Juergen schrieb:
>> p1 dereferenziert ist konstant und vom Typ int.
>
> Das ist falsch, const bezieht sich hier auf int.

Passt schon. Er wird ja erst deferenziert und ist dann erst konstant. 
Sprachlich holpert das eben etwas, man muss da geistige "sequence 
points" bei der Zerlegung einfügen.

Intention von Ritche war es, die Deklaration übersichtlich zu machen, 
damit der Programmierer es einfacher hat. Aber bekanntlich ist "gut 
gemeint" das Gegenteil von "gut gemacht" und das heutige Ergebnis ist, 
dass der Mensch wie ein Compiler denken muss, um diesen Mist überhaupt 
verstehen zu können.

von Gelöscht (kami89)


Lesenswert?

Wir haben es so gelernt, dass man immer von rechts nach links lesen 
soll.

int const * p1;
p1 ist ein Pointer auf ein konstanter Int.

const int * p1;
p1 ist ein Pointer auf eine Int-Konstante.

int * const p1;
p1 ist ein konstanter Pointer auf ein Int.

const int * const p1;
p1 ist ein konstanter Pointer auf eine Int-Konstante.

mfg

von (prx) A. K. (prx)


Lesenswert?

Urban B. schrieb:
> Wir haben es so gelernt, dass man immer von rechts nach links lesen
> soll.

Was ist demzufolge
  int (*p)[];

Nope. Von innen nach aussen ist der korrekte Weg. Wobei es bei komplexen 
Type-Casts mangels Namen als Anker nicht unbedingt einfach sein muss, 
das innerste "innen" überhaupt zu finden.

von Gelöscht (kami89)


Lesenswert?

A. K. schrieb:
> Urban B. schrieb:
>> Wir haben es so gelernt, dass man immer von rechts nach links lesen
>> soll.
>
> Was ist demzufolge
>   int (*p)[];

Diese Eselsbrücke ist nur für const-Sachen gedacht, also um rauszufinden 
ob nun der Pointer oder die Variable selbst konstant ist (oder beides) 
;-) Darum ging es ja in den letzten paar Beiträgen, deshalb habe ich 
diese Eselsbrücke mal erwähnen wollen.

von (prx) A. K. (prx)


Lesenswert?

Ach ja, wo wir grad dabei sind. Wo fängt man bei diesem Ausdruck mit der 
Zerlegung an?
  (void(*(*)(int,void(*)(int)))(int))(f)

von Robert L. (lrlr)


Lesenswert?

> Wo fängt man bei diesem Ausdruck mit der
>Zerlegung an?

als erstes Zerlegt man mal den Programmierer.. ;-)

von Roland H. (batchman)


Lesenswert?

Juergen schrieb:
> Manche Leute schreiben die Qualifier auch einheitlich rechts neben den
> Typ, auf den sie sich beziehen.

Ich bin inzwischen einer davon. Und leide noch heute darunter, dass 
Teile der Code-Basis noch nicht konvertiert wurden.

Richtig toll ist das nicht, aber immerhin kann man dann die "qualifier" 
konsistent schreiben:

Zitat von 
http://stackoverflow.com/questions/2478151/why-is-volatile-parasitic-in-c

Always write the const / volatile qualifier after what you wanted to 
qualify. It is the only way to write qualifiers consistently, because 
you can write both volatile int and int volatile when you want a 
volatile integer, but only int * volatile will give you a volatile 
pointer.

von Rolf Magnus (Gast)


Lesenswert?

Yalu X. schrieb:
> Rolf Magnus schrieb:
>> Das ist aber falsch.
>
> Ich glaube eher, wie haben eine Unterschiedliche Leseweise für
> Typdeklarationen.

Ja, du versuchst, die vermutlich ursprünglich gedachte Leseweise 
anzuwenden, ich versuche, die durch was zu ersetzen, das für mich mehr 
Sinn ergibt. Keine Ahnung, welche Variante nun die bessere ist. ;-)

> Mit meiner oben vorgeschlagenen "Von-Innen-Nach-Außen-Methode" (die IMHO
> auch Ritchie bei der Konzeptioen der Deklarationssystax im Kopf gehabt
> hat) lese ich das so:
>
> p1 dereferenziert ist konstant und vom Typ int.

Damit hab ich insofern schon meine Schwierigkeit, als daß für mich das * 
hier in der Deklaration ausschließlich die Bedeutung "Zeiger auf das, 
was davor steht" hat, während das dereferenzierungs-Sternchen damit 
erstmal direkt nichts zu tun hat.
Daß die Dereferenzierung und die Zeigerdeklaration das selbe Zeichen 
nutzen ist natürlich Absicht und resultiert wohl aus der von dir 
angegebenen Leseweise, aber ich finde das unlogisch, weil es zwei ganz 
verschiedene Dinge sind.

> Man könnte auch sagen, das const bezieht sich auf die Membervariablen,
> die von der Methode nicht verändert werden.

Genauer gesagt beziehen sie sich auf den this-Pointer innerhalb der 
Funktion. Dieser ist dann nämlich ein Zeiger auf const. Alle Zugriffe 
auf die Membervariablen werden (entweder implizit oder explizit) über 
diesen durchgeführt, also sind sie speziell in dieser Funktion auch alle 
const.

> Dadurch, dass rechts vom const nichts steht, wird angezeigt, dass die
> konstanten Dinge woanders stehen, nämlich dort, wor die Membervariablen
> deklariert sind.

Aber dennoch bezieht sich das const nicht ganz allegemin auf die 
Membervariablen, sondern nur auf Zugriffe auf diese aus der Funktion 
heraus.

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.