Combien et quelles sont les utilisations de «const» en C ++?

129

En tant que programmeur C ++ novice, il y a des constructions qui me semblent encore très obscures, l'une d'entre elles l'est const. Vous pouvez l'utiliser dans tant d'endroits et avec tant d'effets différents qu'il est presque impossible pour un débutant de sortir vivant. Est-ce qu'un gourou du C ++ expliquera une fois pour toujours les différentes utilisations et si et / ou pourquoi ne pas les utiliser?

Tunnuz
la source
exactement à la recherche de cette question: D
alamin

Réponses:

100

Essayer de collecter quelques utilisations:

Liaison de certains temporaires à reference-to-const, pour allonger sa durée de vie. La référence peut être une base - et son destructeur n'a pas besoin d'être virtuel - le bon destructeur est toujours appelé:

ScopeGuard const& guard = MakeGuard(&cleanUpFunction);

Explication , en utilisant le code:

struct ScopeGuard { 
    ~ScopeGuard() { } // not virtual
};

template<typename T> struct Derived : ScopeGuard { 
    T t; 
    Derived(T t):t(t) { }
    ~Derived() {
        t(); // call function
    }
};

template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }

Cette astuce est utilisée dans la classe utilitaire ScopeGuard d'Alexandrescu. Une fois que le temporaire est hors de portée, le destructeur de Derived est appelé correctement. Le code ci-dessus manque quelques petits détails, mais c'est le gros problème.


Utilisez const pour indiquer aux autres méthodes de ne pas modifier l'état logique de cet objet.

struct SmartPtr {
    int getCopies() const { return mCopiesMade; }
};

Utilisez const pour les classes de copie sur écriture , pour que le compilateur vous aide à décider quand et quand vous devez copier.

struct MyString {
    char * getData() { /* copy: caller might write */ return mData; }
    char const* getData() const { return mData; }
};

Explication : Vous souhaiterez peut-être partager des données lorsque vous copiez quelque chose tant que les données de l'objet d'origine et de l'objet copié restent les mêmes. Une fois que l'un des objets change de données, vous avez cependant besoin de deux versions: une pour l'original et une pour la copie. Autrement dit, vous copiez sur une écriture dans l'un ou l'autre objet, de sorte qu'ils aient maintenant tous les deux leur propre version.

Utilisation du code :

int main() {
    string const a = "1234";
    string const b = a;
    // outputs the same address for COW strings
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

L'extrait de code ci-dessus imprime la même adresse sur mon GCC, car la bibliothèque C ++ utilisée implémente une copie sur écriture std::string. Les deux chaînes, même s'il s'agit d'objets distincts, partagent la même mémoire pour leurs données de chaîne. Faire bnon-const préférera la version non-const de operator[]et GCC créera une copie du tampon de la mémoire de sauvegarde, car nous pourrions le changer et cela ne doit pas affecter les données de a!

int main() {
    string const a = "1234";
    string b = a;
    // outputs different addresses!
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Pour que le constructeur de copie fasse des copies à partir des objets const et des temporaires :

struct MyClass {
    MyClass(MyClass const& that) { /* make copy of that */ }
};

Pour créer des constantes qui ne peuvent pratiquement pas changer

double const PI = 3.1415;

Pour passer des objets arbitraires par référence plutôt que par valeur - pour éviter le passage par valeur éventuellement coûteux ou impossible

void PrintIt(Object const& obj) {
    // ...
}
Johannes Schaub - litb
la source
2
Pouvez-vous expliquer s'il vous plaît la première et la troisième utilisation dans vos exemples?
tunnuz
"Pour garantir à l'appelé que le paramètre ne peut pas être NULL" Je ne vois pas comment const a quelque chose à voir avec cet exemple.
Logan Capaldo
oups, j'échoue donc. J'ai en quelque sorte commencé à écrire sur les références. merci beaucoup d'avoir gémi :) je vais bien sûr supprimer ce truc maintenant :)
Johannes Schaub - litb
3
Veuillez expliquer le premier exemple. Cela n'a pas beaucoup de sens pour moi.
chikuba
28

Il y a vraiment 2 utilisations principales de const en C ++.

Valeurs const

Si une valeur se présente sous la forme d'une variable, d'un membre ou d'un paramètre qui ne sera pas (ou ne devrait pas) être modifié pendant sa durée de vie, vous devez la marquer comme const. Cela permet d'éviter les mutations sur l'objet. Par exemple, dans la fonction suivante, je n'ai pas besoin de modifier l'instance Student passée, je la marque donc const.

void PrintStudent(const Student& student) {
  cout << student.GetName();
}

Quant à savoir pourquoi vous feriez cela. Il est beaucoup plus facile de raisonner sur un algorithme si vous savez que les données sous-jacentes ne peuvent pas changer. "const" aide, mais ne garantit pas que cela sera réalisé.

Évidemment, l'impression de données sur le cout ne demande pas beaucoup de réflexion :)

Marquer une méthode membre comme const

Dans l'exemple précédent, j'ai marqué Student comme const. Mais comment C ++ a-t-il su que l'appel de la méthode GetName () sur étudiant ne muterait pas l'objet? La réponse est que la méthode a été marquée comme const.

class Student {
  public:
    string GetName() const { ... }
};

Marquer une méthode "const" fait 2 choses. Principalement, il indique à C ++ que cette méthode ne mute pas mon objet. La deuxième chose est que toutes les variables membres seront désormais traitées comme si elles étaient marquées comme const. Cela aide mais ne vous empêche pas de modifier l'instance de votre classe.

C'est un exemple extrêmement simple, mais j'espère qu'il vous aidera à répondre à vos questions.

JaredPar
la source
16

Veillez à bien comprendre la différence entre ces 4 déclarations:

Les 2 déclarations suivantes sont identiques sémantiquement. Vous pouvez changer l' endroit où pointent ccp1 et ccp2, mais vous ne pouvez pas changer l'élément sur lequel ils pointent.

const char* ccp1;
char const* ccp2;

Ensuite, le pointeur est const, donc pour être significatif, il doit être initialisé pour pointer vers quelque chose. Vous ne pouvez pas le faire pointer vers autre chose, mais ce qu'il indique peut être changé.

char* const cpc = &something_possibly_not_const;

Enfin, nous combinons les deux - ainsi l'objet pointé ne peut pas être modifié et le pointeur ne peut pointer nulle part ailleurs.

const char* const ccpc = &const_obj;

La règle de la spirale dans le sens des aiguilles d'une montre peut aider à démêler une déclaration http://c-faq.com/decl/spiral.anderson.html

Steve Folly
la source
De manière détournée, oui! La règle de la spirale dans le sens des aiguilles d'une montre le décrit mieux - commencez par le nom (kpPointer) et tracez une spirale dans le sens des aiguilles d'une montre passant par le jeton, et dites chaque jeton. Evidemment, il n'y a rien à droite de kpPointer mais ça marche toujours.
Steve Folly
3

En guise de petite note, comme je l'ai lu ici , il est utile de remarquer que

const s'applique à tout ce qui est à sa gauche immédiate (sauf s'il n'y a rien, auquel cas il s'applique à tout ce qui est à sa droite immédiate).

JoePerkins
la source