538 Stimmen

Verwendung von 'const' für Funktionsparameter

Wie weit gehen Sie mit const ? Machen Sie einfach Funktionen const wenn es nötig ist, oder gehen Sie aufs Ganze und verwenden es überall? Stellen Sie sich zum Beispiel einen einfachen Mutator vor, der einen einzigen booleschen Parameter annimmt:

void SetValue(const bool b) { my_val_ = b; }

Ist das const tatsächlich nützlich? Ich persönlich entscheide mich dafür, es ausgiebig zu nutzen, einschließlich der Parameter, aber in diesem Fall frage ich mich, ob es sich lohnt?

Ich war auch überrascht zu erfahren, dass man die const von Parametern in einer Funktionsdeklaration, sondern kann sie in die Funktionsdefinition aufnehmen, z. B.:

.h-Datei

void func(int n, long l);

.cpp-Datei

void func(const int n, const long l)

Gibt es dafür einen Grund? Es erscheint mir ein wenig ungewöhnlich.

543voto

rlerallut Punkte 7067

const ist sinnlos, wenn das Argument als Wert übergeben wird, da Sie das Objekt des Aufrufers nicht ändert.

Falsch.

Es geht darum, Ihren Code und Ihre Annahmen selbst zu dokumentieren.

Wenn viele Leute an Ihrem Code arbeiten und Ihre Funktionen nicht trivial sind, sollten Sie die const alles, was Sie können. Wenn Sie Code in Industriestärke schreiben, sollten Sie immer davon ausgehen, dass Ihre Mitarbeiter Psychopathen sind, die versuchen, Sie auf jede erdenkliche Art und Weise zu kriegen (vor allem, weil es oft Sie selbst in der Zukunft sind).

Außerdem, wie bereits jemand erwähnt hat, ist es könnte dem Compiler dabei helfen, die Dinge ein wenig zu optimieren (auch wenn es ein weiter Weg ist).

243voto

Greg Rogers Punkte 34400

Der Grund dafür ist, dass const für den Parameter gilt nur lokal innerhalb der Funktion, da sie mit einer Kopie der Daten arbeitet. Das bedeutet, dass die Funktionssignatur in jedem Fall dieselbe ist. Es ist wahrscheinlich schlechter Stil, dies oft zu tun, obwohl.

Ich persönlich neige dazu, nicht zu verwenden const außer für Referenz- und Zeigerparameter. Bei kopierten Objekten spielt es keine Rolle, obwohl es sicherer sein kann, da es die Absicht innerhalb der Funktion signalisiert. Es ist wirklich eine Ermessensentscheidung. Ich neige dazu, Folgendes zu verwenden const_iterator Ich habe nicht vor, sie zu verändern, also ist es wohl jedem selbst überlassen, solange er const Die Korrektheit von Referenztypen wird konsequent beibehalten.

183voto

Constantin Punkte 26508

Manchmal (zu oft!) muss ich den C++-Code von jemand anderem entwirren. Und wir alle wissen, dass von jemand anderem C++-Code ist fast per Definition ein völliges Durcheinander :) Das erste, was ich tue, um den lokalen Datenfluss zu entschlüsseln, ist const in jeder Variablendefinition, bis der Compiler anfängt zu bellen. Das bedeutet auch const-qualifizierende Wertargumente, denn sie sind nur ausgefallene lokale Variablen, die vom Aufrufer initialisiert werden.

Ah, ich wünschte, Variablen wären const standardmäßig und änderbar war für Nicht-Konst-Variablen erforderlich :)

104voto

Adisak Punkte 6440

Extra Überflüssige Konstanten sind aus API-Sicht schlecht:

Einfügen zusätzlicher überflüssiger const's in Ihren Code für Parameter des intrinsischen Typs, die als Wert übergeben werden verstopft Ihre API ohne dem Aufrufer oder API-Benutzer ein sinnvolles Versprechen zu geben (es erschwert nur die Implementierung).

Zu viele 'const' in einer API, die nicht benötigt werden, sind wie " weinende Wölfe Irgendwann werden die Leute anfangen, den Begriff "const" zu ignorieren, weil er überall vorkommt und meistens nichts bedeutet.

Die "reductio ad absurdum" Argument zu extra consts in API sind gut für diese ersten beiden Punkte wäre, wenn mehr const Parameter gut sind, dann jedes Argument, das eine const auf sie haben kann, SOLLTE eine const auf sie haben. In der Tat, wenn es wirklich so gut wäre, würden Sie wollen, dass const der Standard für Parameter und haben ein Schlüsselwort wie "mutable" nur, wenn Sie den Parameter ändern möchten.

Versuchen wir also, überall, wo es möglich ist, ein Hindernis zu schaffen:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

Betrachten Sie die obige Code-Zeile. Die Deklaration ist nicht nur unübersichtlicher, länger und schwieriger zu lesen, sondern drei der vier "const"-Schlüsselwörter können vom API-Benutzer getrost ignoriert werden. Die zusätzliche Verwendung von "const" hat jedoch dazu geführt, dass die zweite Zeile potenziell GEFÄHRLICH!

Warum?

Ein kurzer Lesefehler beim ersten Parameter char * const buffer könnte den Eindruck erwecken, dass der Speicher im Datenpuffer, der übergeben wird, nicht verändert wird - dies ist jedoch nicht der Fall! Überflüssige 'const' können zu gefährlichen und falschen Annahmen über Ihre API führen wenn sie schnell gescannt oder falsch gelesen werden.


Überflüssige Konstanten sind auch unter dem Gesichtspunkt der Code-Implementierung schlecht:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

Wenn FLEXIBLE_IMPLEMENTATION nicht wahr ist, dann "verspricht" die API, die Funktion nicht auf die erste Art und Weise unten zu implementieren.

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

Das ist ein sehr dummes Versprechen. Warum sollten Sie ein Versprechen geben, das Ihrem Aufrufer keinerlei Nutzen bringt und nur Ihre Implementierung einschränkt?

Beides sind absolut gültige Implementierungen derselben Funktion, so dass Sie sich nur unnötig eine Hand auf den Rücken gebunden haben.

Außerdem ist es ein sehr oberflächliches Versprechen, das leicht (und rechtlich) umgangen werden kann.

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

Ich habe es trotzdem so implementiert, obwohl ich versprochen hatte, es nicht zu tun - nur mit einer Wrapper-Funktion. Das ist so, wie wenn der Bösewicht in einem Film verspricht, niemanden zu töten und stattdessen seinen Handlangern befiehlt, ihn zu töten.

Diese überflüssigen Consts sind nicht mehr wert als ein Versprechen eines Film-Bösewichts.


Aber die Fähigkeit zu lügen wird noch schlimmer:

Ich bin darüber aufgeklärt worden, dass man const im Header (Deklaration) und im Code (Definition) durch die Verwendung von spurious const falsch zuordnen kann. Die Befürworter von const behaupten, dass dies eine gute Sache ist, da man const nur in der Definition verwenden kann.

// Example of const only in definition, not declaration
struct foo { void test(int *pi); };
void foo::test(int * const pi) { }

Das Gegenteil ist jedoch der Fall: Sie können eine falsche Konstante nur in die Deklaration aufnehmen und sie in der Definition ignorieren. Dies macht überflüssige const in einer API nur noch schrecklicher und zu einer furchtbaren Lüge - siehe dieses Beispiel:

struct foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

Alles, was die überflüssige const bewirkt, ist, dass der Code des Implementierers weniger lesbar wird, da er gezwungen ist, eine weitere lokale Kopie oder eine Wrapper-Funktion zu verwenden, wenn er die Variable ändern oder die Variable durch eine Nicht-Konst-Referenz übergeben will.

Sehen Sie sich dieses Beispiel an. Was ist besser lesbar? Ist es offensichtlich, dass der einzige Grund für die zusätzliche Variable in der zweiten Funktion darin besteht, dass ein API-Designer eine überflüssige Konstante eingefügt hat?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

Hoffentlich haben wir daraus etwas gelernt. Überflüssige const ist ein API-verstopfender Schandfleck, ein lästiges Ärgernis, ein oberflächliches und bedeutungsloses Versprechen, ein unnötiges Hindernis und führt gelegentlich zu sehr gefährlichen Fehlern.

97voto

Ben Straub Punkte 5437

Die beiden folgenden Zeilen sind funktional gleichwertig:

int foo (int a);
int foo (const int a);

Offensichtlich können Sie keine Änderungen vornehmen a im Körper von foo wenn es auf die zweite Art definiert ist, aber von außen betrachtet gibt es keinen Unterschied.

Wo const ist bei Referenz- oder Zeigerparametern besonders nützlich:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

Das bedeutet, dass foo einen großen Parameter, vielleicht eine Datenstruktur mit einer Größe von Gigabytes, annehmen kann, ohne ihn zu kopieren. Außerdem sagt es dem Aufrufer: "Foo wird den Inhalt dieses Parameters nicht* ändern." Die Übergabe einer const-Referenz ermöglicht es dem Compiler auch, bestimmte Leistungsentscheidungen zu treffen.

*: Es sei denn, es beseitigt die Konstante, aber das ist ein anderes Thema.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X