3 notation pour un même résultat...

... je me doute qu'il y a des raisons historiques mais bon... j'aimerais savoir.

a marqué ce sujet comme résolu.

Bonjour, pour commencer un tout petit bout de code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream> 

using namespace std;

int main()
{
    int i = 123;
    int j (123);
    int k {123};
    cout << i << endl;
    cout << j << endl;
    cout << k << endl;

    return 0;
}

J'ai lu 3 cours de C++ et dans les 3 cas, on m'a indiqué une façon différente d'initialiser les variables, j'ai peur d'en lire un autre au cas ou il existe une infinité de façon d'indiquer la valeur initiale d'une variable.

Je me demande simplement pourquoi, si quelqu'un a le temps de me l'expliquer.

il existe une infinité

Tout de même pas !

La version int i = 123; est la plus communément (même la seule) utilisé pour initialiser un nombre, un caractère, un booléen… bref n'importe quel type primitif du langage ! Les chaînes de caractère de type char* ou string conviennent aussi.

La notation int j (123); est très fréquemment employée : elle sert à initialiser des objets, entre parenthèses tu mets la liste des arguments que tu donnes au constructeur de ton objet. En général ça donne ça : NomDeTaClasse nomDeTaVariable(arg1, arg2, …)
Cette notation est à bannir pour les types primitifs !

Enfin, la notation int k {123}; qui utilise {} il vaut mieux la réserver pour l'initialisation d'un tableau vector ou array, d'une list etc… ça sous-entend que tu insères une collection d'éléments.

Ce ne sont que des conventions, mais en les respectant tu y verras plus clair.

+0 -4

Si je me fie à la documentation sur cppreference.com (version anglaise), il y a 9 types d'initialisation.

Alors pour un int, je pense que tu as cité toutes les méthodes raisonnablement possibles, à l'exception de l'initialisation par défaut (int i;) qu'il faut éviter pour les types tels que ìnt pour éviter de se retrouver avec une variable non initialisé.

En revanche, pour les objets, la sémantique est bien plus complexe. Il faut d'abord savoir que std::string s(); ne fait pas du tout ce à quoi on peut s'attendre au premier abord. En fait, la syntaxe est ambigüe puisqu'il peut s'agir soit de la déclaration d'une variable de type std::string en ne donnant aucun argument au constructeur, soit de la déclaration d'une fonction s qui retourne un std::string et qui ne prend aucun argument. C'est cette deuxième possibilité qui est utilisé dans les spécifications. Ce problème arrive aussi pour des trucs un poil plus subtils : std::string s(std::string());. Encore une fois, c'est ambigüe puisqu'il peut s'agir soit d'une déclaration de fonction, soit d'une déclaration de variable.

Il est possible de résoudre cette ambigüité en ajoutant des parenthèses supplémentaires : std::string s((std::string())); défini toujours une variable et jamais une fonction.

Pour un objet, la plupart du temps std::string s; et std::string s{}; auront le même effet et appellent le constructeur par défaut. std::string s(); ne fait pas ce que tu veux.

std::string s("hello") appel le constructeur de std::string qui prend en paramètre un char*. Note, c'est dans ce cas qu'il faut faire attention aux possibles ambigüités.

std::string s = "hello"; vérifie que "hello" peut se convertir en std::string, fait la conversion et utilise le constructeur par copie de std::string pour initialiser s. Pour le coup, c'est très souvent directement optimisé par le compilateur pour donner les mêmes performances que std::string s("hello");.

std::string s{"hello"} est une syntaxe qui permet d'initialiser s avec une valeur. Pour une chaîne de caractère, on ne voit pas trop la différence avec std::string s("hello") mais avec un type comme std::vector, c'est beaucoup plus net.

std::vector<int> v(1, 2, 3, 4, 5); ne fonctionne pas : il n'y a pas de constructeur qui prend en paramètre 5 entiers. En revanche std::vector<int> v{1, 2, 3, 4, 5}; fonctionne en appelant le constructeur de std::vector qui prend en paramètre une liste d'initialisation (ajouté en C++11).

Si la liste d'initialisation ne convient pas (par exemple pour std::vector<int> v2{v1.begin(), v1.end()};), le compilateur utilise le constructeur qui convient (comme si on utilisait des parenthèses).

On pourrait donc en conclure qu'utiliser les accolades permet d'éviter les ambigüités et fait au moins autant que les parenthèses, mais ce n'est pas exacte. En effet std::vector<int> v(10); créé un vecteur de 10 int, alors que std::vector<int> v{10}; créé un vecteur avec un seul int (qui vaut 10).

Bon, j'espère que je n'ai pas fait d'erreur et que j'ai été assez clair sur ce qui fonctionne et ce qui ne fonctionne pas.

Les 3 formes sont plus au moins équivalente avec une nette préférence à la 3ème.

  • int i = 123: implique une construction implicite. On pourrait croire que cela utilise l'opérateur = pour initialiser l'objet, mais non, cette forme appel bel et bien un constructeur. Je ne sais pas si tu as vu les structures et classes, mais un constructeur peut être explicite et cette forme ne fonctionne donc plus.
  • int j(123): construction explicite de j. Cette forme à le défaut de ne plus dire la même chose lorsqu'il n'y a pas de paramètre. int j() définit un prototype de fonction. Elle à aussi le défaut d'être mal interprété dans certaine situation: T j(uneclase()) = toujours un prototype de fonction T j((uneclasse())) = j construit avec un paramètre de type uneclasse.
  • int k{123}: La forme universelle introduit en C++11. Elle n'a pas le défaut des constructions parenthésés et permet d'initialiser certains types même s'ils n'ont pas de constructeur. Cette forme est aussi plus courte pour construire des objets à la volé (foo({x,y,z}) plutôt foo(Point(x,y,z)) (foo(Point{x,y,z}) fonctionne aussi)). Dernièrement, le compilateur vérifie le narrowing, ex. char c{123456} ne rentre pas dans un char, le compilateur lance une erreur.

Il existe la forme avec auto pour que le compilateur déduise le type grâce à la valeur d'initialisation.

  • auto i{123}: i est un int
  • auto i{123u}: i est un unsigned
  • auto i = {123}: i est un std::initializer_list ou un int… Dépend de la norme, je déconseille de l'utiliser.

EDIT: Un truc à savoir avec std::vector<T>{x, y}. Cette forme n’appellera jamais le constructeur 2 si T est convertible en std::size_t. Simplement car une classe avec un constructeur prenant un initializer_list est prioritaire sur la forme n'en prenant pas. Même si une conversion implicite soit être effectué. Obligé d'utiliser la forme parenthésé dans cette situation.

+0 -0

Personnellement, pour ne pas me prendre la tête avec les initialisations, j'ai un "algo" assez simple :

  • l'élément que je veux instancier reçoit un retour de fonction, une autre variable ou que sais-je : auto truc = expression .
  • l'élément n'a pas de dépendances (ou je veux forcer le type) :
    • la classe ne déclare pas de constructeur par liste d'initialisation : type truc{/*valeur(s) potentielles*/}.
    • la classe en déclare un. J'ai une liste : type truc { liste }, je n'en ai pas, paranthèses.

Comme dans 90% des cas, on a une dépendance, on se prend rarement la tête avec le type. Comme les classes qui déclare des constructeurs par liste d'initialisation ne sont pas franchement les plus nombreuses, on utilise rarement les parenthèses et très souvent les crochets.

Enfin, la notation int k {123}; qui utilise {} il vaut mieux la réserver pour l'initialisation d'un tableau vector ou array, d'une list etc… ça sous-entend que tu insères une collection d'éléments.

Ce ne sont que des conventions, mais en les respectant tu y verras plus clair.

Algue-Rythme

La notation {} a été introduite justement pour uniformiser les différentes notations pour les appels de fonctions, en particulier les constructeurs. C'est plutôt vers cette notation qu'il faudrait s'orienter en priorité.

Outre l'argument pûrement esthétique de l'uniformité, il y a des arguments techniques qui me font préférer cette notion par défaut.

+2 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte