En tant que programmeur C++ débutant, il y a certaines constructions qui me semblent encore très obscures, l'une d'entre elles est la suivante const
. Vous pouvez l'utiliser dans tellement d'endroits et avec tellement d'effets différents qu'il est presque impossible pour un débutant d'en sortir vivant. Est-ce qu'un gourou du C++ pourra expliquer une fois pour toutes les différentes utilisations et si et/ou pourquoi ne pas les utiliser ?
Réponses
Trop de publicités?J'essaie de rassembler quelques utilisations :
Lier un temporaire à la référence-à-const, pour allonger sa durée de vie. La référence peut être une base - et le destructeur de celle-ci 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 sort de la 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 qu'elles ne changeront pas l'état logique de cet objet.
struct SmartPtr {
int getCopies() const { return mCopiesMade; }
};
Utiliser const pour les classes de copie en é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 pouvez vouloir 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. Dès 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 copie sur un écrire à l'un ou l'autre des objets, de sorte qu'ils ont maintenant tous 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 ci-dessus imprime la même adresse sur mon GCC, parce que la bibliothèque C++ utilisée implémente une copie sur écriture. std::string
. Les deux chaînes, même si elles sont des objets distincts, partagent la même mémoire pour leurs données de chaîne. Création de b
non-const préférera la version non-const de l'option operator[]
et GCC créera une copie du tampon de mémoire de sauvegarde, car nous pourrions le modifier 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 copies puisse faire des copies à partir d'objets constants et temporaires. :
struct MyClass {
MyClass(MyClass const& that) { /* make copy of that */ }
};
Pour créer des constantes qui ne peuvent trivialement pas changer
double const PI = 3.1415;
Pour passer des objets arbitraires par référence plutôt que par valeur - pour éviter les passages par valeur, éventuellement coûteux ou impossibles.
void PrintIt(Object const& obj) {
// ...
}
Il y a vraiment 2 utilisations principales de const en C++.
Valeurs constantes
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 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 transmise et je la marque donc const.
void PrintStudent(const Student& student) {
cout << student.GetName();
}
Quant à savoir pourquoi vous feriez ça. 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é.
De toute évidence, l'impression de données vers 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 étant const. Mais comment le C++ pouvait-il savoir que l'appel de la méthode GetName() sur student ne modifierait pas l'objet ? La réponse est que la méthode était marquée comme const.
class Student {
public:
string GetName() const { ... }
};
Le fait de marquer une méthode "const" a deux effets. Premièrement, cela indique au C++ que cette méthode ne modifiera pas mon objet. La deuxième chose est que toutes les variables membres seront maintenant traitées comme si elles étaient marquées comme const. Cela vous aide mais ne vous empêche pas de modifier l'instance de votre classe.
Il s'agit d'un exemple extrêmement simple, mais nous espérons qu'il vous aidera à répondre à vos questions.
J'ai trouvé cela utile Parashift Const Correctness
Prenez soin de comprendre la différence entre ces 4 déclarations :
Les 2 déclarations suivantes sont sémantiquement identiques. Vous pouvez changer où ccp1 et ccp2 pointent, mais vous ne pouvez pas changer la chose qu'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 quelque chose d'autre, mais la chose vers laquelle il pointe doit être initialisée. puede être modifié.
char* const cpc = &something_possibly_not_const;
Enfin, nous combinons les deux, de sorte que l'objet pointé ne peut pas être modifié et que le pointeur ne peut pas pointer 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
J'ai trouvé "La déclaration 'const' de C++ : Pourquoi et comment" pour être utile. J'espère que cela vous aidera