En tant que programmeur C++ novice, certaines constructions me semblent encore très obscures, dont const
. Vous pouvez l'utiliser dans tant d'endroits et avec tellement d'effets différents qu'il est presque impossible pour un débutant de sortir vivant. Certains gourous du C++ expliqueront-ils une fois pour toutes les diverses utilisations et si et/ou pourquoi ne pas les utiliser?
Essayer de collecter certaines utilisations:
Lier un temporaire à une référence à 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 destructeur droit 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 d'utilitaires 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.
tilisez const pour dire aux autres méthodes ne changera pas l'état logique de cet objet.
struct SmartPtr {
int getCopies() const { return mCopiesMade; }
};
tilisez const pour les classes de copie sur écriture, pour que le compilateur vous aide à décider quand et quand vous n'avez pas besoin de 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 a modifié les données, il vous faut cependant maintenant deux versions: une pour l'original et une pour la copie. Autrement dit, vous copiez sur un écrivez dans l'un ou l'autre objet, de sorte que ils ont maintenant tous les deux leur propre version.
À l'aide de 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, 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 de b
non-const préférera la version non-const de operator[]
et GCC créera une copie de la mémoire tampon de sauvegarde, car nous pourrions la 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 copie fasse des copies à partir d'objets const et temporaires:
struct MyClass {
MyClass(MyClass const& that) { /* make copy of that */ }
};
Pour faire des constantes qui ne peuvent pas changer trivialement
double const PI = 3.1415;
Pour passer des objets arbitraires par référence au lieu de par valeur - pour éviter le passage de sous-valeur éventuellement coûteux ou impossible
void PrintIt(Object const& obj) {
// ...
}
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 const. Cela permet d'éviter les mutations sur l'objet. Par exemple, dans la fonction suivante, je n'ai pas besoin de changer 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 atteint.
De toute évidence, l'impression de données sur cout ne nécessite pas beaucoup de réflexion :)
Marquage d'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. Il indique principalement à 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 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 j'espère qu'il vous aidera à répondre à vos questions.
Prenez soin de comprendre la différence entre ces 4 déclarations:
Les 2 déclarations suivantes sont identiques sémantiquement. Vous pouvez changer où point ccp1 et ccp2, 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 autre chose, mais la chose qu'il pointe vers peut être modifiée.
char* const cpc = &something_possibly_not_const;
Enfin, nous combinons les deux - de sorte que la chose pointée ne peut pas être modifiée et que le pointeur ne peut pointer nulle part ailleurs.
const char* const ccpc = &const_obj;
La règle en spirale dans le sens horaire peut aider à démêler une déclaration http://c-faq.com/decl/spiral.anderson.html
Comme une petite note, comme je l'ai lu ici , il est utile de noter que
const
s'applique à tout ce qui est à sa gauche immédiate (sauf s'il n'y a rien dans ce cas, il s'applique à ce qui est à sa droite immédiate).