C++ - Le constructeur implicite

Bonjour,
Je crois qu'un constructeur implicite est généré automatiquement par le compilateur si dans une classe aucun constructeur explicite n'est crée par le programmeur. Hors dans mes recherches, je trouve parfois ce genre de code :

class Objets
{
  public:
    Objets() = delete;
    Objets (double c1, double c2);

Il semble même parfois que le constructeur implicite devienne le constructeur par défaut même en présence d'un constructeur explicite : Objets() = default;

d'où cette question :

  • à partir du moment où un constructeur explicite est défini par le programmeur quel est l'interêt de supprimer le constructeur implicite ou de le définir par défaut ?

Merci
Bonne journée.

Lorsque vous définissez un constructeur explicite dans une classe, le compilateur ne génère plus de constructeur par défaut, ce qui signifie qu'aucun constructeur implicite ne sera disponible pour créer des instances de cette classe sans paramètres.

Quand vous faites Objets() = delete;, c'est souvent utilisée pour apporter une clarification au développement. Cela est utile lorsque la classe ne peut pas fonctionner de manière significative sans certains paramètres (par exemple, cela peut aider à éviter des erreurs où un objet est créé sans les données nécessaires à son bon fonctionnement) ➜ En gros, vous dites que vous n'avez pas de constructeur implicite (qui n'aurait pas été créé effectivement si vous avez un autre constructeur).

Notez que vous pouvez faire Objets() = default; ➜ vous indiquez explicitement que vous souhaitez que le compilateur génère un constructeur par défaut pour votre classe. Cela rend votre intention explicite. Même si le compilateur aurait généré un constructeur par défaut automatiquement (s'il n'y avait pas d'autres constructeurs définis), en le déclarant explicitement, vous montrez que vous avez réfléchi à l'instanciation de votre classe et que vous souhaitez offrir cette option (et éventuellement d'autres constructeurs avec paramètres).

Oui, mais ce n'est pas tout à fait ça. En C++ l'appel du constructeur est toujours implicite Quand tu crées une classe, il est obligatoire d'avoir un constructeur par défaut (au minimum) mais tu n'es pas obligé de faire ce constructeur. Dans ce cas c'est le compilateur qui crée le constructeur par défaut du fait qu'il n'existe pas de constructeur par défaut il est appelé constructeur par défaut par défaut.

Quand tu fais cette opération tu crées un constructeur par défaut, le compilateur ne crée pas de constructeur par défaut par défaut

class Objets
{
  public:
    Objets();
};

Le compilateur ne crée donc pas de constructeur par défaut (par défaut)

Quand tu fais cette opération tu crées un constructeur de transtypage, le compilateur ne crée pas de constructeur par défaut par défaut car le constructeur de transtypage est plus avancé que le constructeur par défaut

constructeurObjets (double c1, double c2);

On ne dit pas “constructeur par défaut par défaut”. On parle simplement de “constructeur par défaut”, qu’il soit généré implicitement par le compilateur ou défini explicitement par le programmeur.

En C++, vous avez trois possibilités pour définir un constructeur “de base", chacune ayant des conséquences différentes, surtout si vous fournissez un autre constructeur.

La première option est de laisser le compilateur générer automatiquement un constructeur par défaut. Celui-ci appellera les constructeurs par défaut des membres de la classe et gérera les initialisations basiques. Toutefois, si vous définissez un autre constructeur avec des paramètres, ce constructeur par défaut ne sera plus généré automatiquement, ce qui signifie que vous ne pourrez plus créer une instance de la classe sans arguments.

La deuxième option est d’utiliser la mention = default, qui indique explicitement que vous voulez conserver un constructeur par défaut, même si vous avez défini d’autres constructeurs. Cela permet de maintenir le comportement généré par le compilateur, tout en rendant l’intention plus claire dans le code. De plus, cela permet au compilateur d’effectuer des optimisations et de gérer correctement l’initialisation des membres.

La troisième option consiste à définir manuellement un constructeur sans paramètres. Un constructeur vide peut empêcher certaines optimisations du compilateur et ne bénéficie pas des comportements par défaut, comme l’initialisation automatique des membres. Donc a utiliser avec précaution.

Le constructeur par défaut est le nom du constructeur sans paramètre, il est dit par défaut par défaut quand il généré automatiquement par le compilateur. Le compilateur ne sait générer qu'un constructeur par défaut.
Le constructeur est dit de transtypage lorsqu'il contient des paramètres.
Le compilateur ne définit plus de constructeur dès que l'on crée un constructeur qu'il soit constructeur par défaut ou constructeur de transtypage. Il est possible d'obliger le compilateur à recréer ce constructeur (perso je n'en ai jamais vu l'utilisation).
Comme toutes méthodes le constructeur peut être surchargé et contenir des valeurs par défaut.
A partir de l'exemple initial

class Objets
{
  public:
    Objets();
    Objets (double c1, double c2);
    Objets (float c1, float c2);
    Objets (int c1, int c2);

Soit un constructeur par défaut et trois constructeurs de transtypage

Ok - personnellement je n’ai jamais vu ce terme dans la littérature. On parle souvent de constructeur implicite.

Je vais essayer de le dire autrement

Un constructeur par défaut est effectivement un constructeur sans paramètres. Cependant, il est “par défaut” uniquement lorsqu’il est généré automatiquement par le compilateur si aucun autre constructeur n’est défini. On le dit alors aussi implicite.

Mais si vous définissez explicitement un constructeur sans paramètres, il sera aussi un constructeur par défaut (mais explicite).

Le terme Constructeur de transtypage n’existe pas dans la norme en C++, on parle de constructeur de conversion . Vous semblez faire référence aux constructeurs avec un seul paramètre, qui permettent de convertir un type de données en une instance de la classe, et peuvent donc être utilisés pour la “transtypage” implicite ou explicite (conversion de type). Toutefois, cela n’est pas une terminologie standard.

Je dirais que

  • Le constructeur par défaut ou impicite est un constructeur sans paramètres, et il est généré automatiquement par le compilateur si aucun constructeur n’est défini explicitement.

  • Un constructeur avec paramètres permet d’initialiser un objet avec des valeurs spécifiques et peut être utilisé pour des conversions implicites (si le mot-clé explicit n’est pas utilisé).

L mot-clé explicit est utilisé devant un constructeur pour empêcher les conversions implicites non désirées entre types. Lorsque vous définissez un constructeur qui prend un paramètre, il peut être utilisé pour convertir implicitement un objet d’un autre type en un objet de votre classe. Le mot-clé explicit empêche cette conversion automatique, ne permettant l’initialisation qu’à travers une conversion explicite (par exemple, avec un appel direct au constructeur).

Exemple sans explicit :

class MaClasse {
public:
    MaClasse(int x) {}
};

MaClasse obj = 5;  // Conversion implicite de l'entier 5 en MaClasse

Dans cet exemple, l’entier 5 est automatiquement converti en un objet de type MaClasse grâce au constructeur qui prend un paramètre int.

Exemple avec explicit :

class MaClasse {
public:
    explicit MaClasse(int x) {}
};

MaClasse obj = 5;  // Erreur de compilation

Ici, l’utilisation du mot-clé explicit empêche cette conversion implicite. Pour créer l’objet, vous devez appeler explicitement le constructeur :

MaClasse obj(5); // Correct

Cela permet de mieux contrôler la manière dont les objets sont créés et évite des erreurs involontaires liées aux conversions implicites de types.

Ça date de C++98 et ça a été étendu avec C++11 que nous utilisons avec Arduino

Bonjour @J-M-L,

J'ai parfaitement compris votre explication.
Merci beaucoup et bonne journée.

Bonjour @le_viking,
Je ne connais que quatre types de constructeurs :

  • par défaut : pas d'arguments ;
  • par copie : un argument unique faisant référence à une instance de la classe ;
  • de transtypage : un seul argument ;
  • les autres constructeurs avec plusieurs arguments ;
    Objets (double c1, double c2);
    Objets (float c1, float c2);
    Objets (int c1, int c2);

Ce ne sont pas des constructeurs de transtypage il me semble car ils possèdent plusieurs arguments. D'autre part “constructeur par défaut par défaut” est une expression que je n'ai jamais vu ...

Bon c'est mon interprétation de débutant, elle vaut ce qu'elle vaut :wink:

Très bonne journée à toi et merci.

La norme dit « de conversion » si on veut être précis :slight_smile:

https://en.cppreference.com/w/cpp/language/converting_constructor

C’est le terme français que j’ai vu le plus souvent mais peut être que transtypage est utilisé maintenant, je ne lis pas trop de trucs en français il est vrai

Oui merci @J-M-L pour cette précision :wink:

J'ai vu passer cette appellation de transtypage plusieurs fois avec des expilcations en Français :wink:

OK - c’est raisonnable comme appellation

Tout à fait d'accord, c'est vrai que l'on réserve le terme de transtypage au constructeur avec un seul argument et le terme constructeur à arguments multiples dès qu'il y au moins deux arguments. Hormis par défaut et par copie les noms dépendent tellement de l’auteur !!

Il faut juste se mettre en mémoire que l'appel du constructeur est implicite en C++. L'appel explicite n'est que rarement utilisé.

C’est une base du C++ que j’ai essayé d’expliquer @ludomac
Il utilise une classe (QLabel) avec deux constructeurs le premier qui prend 1 argument et le second 2 arguments. Ces deux constructeurs contiennent une valeur par défaut.
Le fait de ne pas mettre d’argument
gifVent = new QLabel();
implique l’appel du constructeur à 1 argument et il est erroné de penser que de ne pas avoir mis d'argument fait qu’il n’aura pas de valeur. Il aura la valeur par défaut du constructeur. Il n’y aura pas d’appel à un constructeur par défaut puisqu’il n’existe pas.

je n'ai jamais vu cela personnellement. Vous avez des références où c'est employé ?
la spec dit

Constructors have no names and cannot be called directly. They are invoked when initialization takes place, and they are selected according to the rules of initialization. The constructors without explicit specifier are converting constructors. The constructors with a constexpr specifier make their type a LiteralType. Constructors that may be called without any argument are default constructors. Constructors that take another object of the same type as the argument are copy constructors and move constructors.

Non, je n'ai pas de référence sans rechercher, je n'ai repris que ce que @philippe86220 disait parce que je l'ai déjà lu. Je comprend bien la démarche qui veut différencier les constructeurs à un seul paramètre de ceux à plusieurs pour expliquer leurs utilisations.
Seul le terme par défaut semble être vraiment universel même le terme par copie est parfois dit par recopie et je ne pense pas que ça va s'améliorer avec la traduction automatique.

les termes officiels sont en anglais - on peut effectivement adopter une traduction proche.

  • default constructor
    ➜ Un constructeur par défaut est un constructeur qui peut être appelé sans arguments. S'il n'y a pas de constructeur par défaut déclaré par l'utilisateur, le compilateur déclarera implicitement un constructeur par défaut.

  • copy constructor
    ➜ Un constructeur de copie est un constructeur qui peut être appelé avec un argument du même type de classe et copie le contenu de l'argument sans le modifier.

  • move constructor
    ➜ Un constructeur de déplacement est un constructeur qui peut être appelé avec un argument du même type de classe et copie le contenu de l'argument, en le modifiant éventuellement.

Les constructeurs de copie et de déplacement non explicit, déclarés implicitement ou définis par l'utilisateur, sont des constructeurs de conversion.

  • converting constructor
    ➜ Un constructeur qui n'est pas déclaré avec le spécificateur explicit et qui peut être appelé avec un seul paramètre est appelé un constructeur de conversion (OK pour transtypage si vous voulez)

ça n'apporte rien de plus à la discussion...
J'ai besoin de mettre des mots sur des idées mais aussi du code donc voici un exemple tout bête qui ne sert qu'à moi :

class Objet
{
  public:
    double leChiffre;
    Objet(); // constructeur par défaut
    Objet(double c1); // constructeur de transtypage
    double voirLeChiffre(); //méthode de classe
};

Objet::Objet() {}
Objet::Objet(double c1)
{
  leChiffre = c1;
}

double Objet::voirLeChiffre()
{
  return this->leChiffre;
}

void setup() {
  Serial.begin(115200);
  // Objet monInstance(5); //appel implicite du constructeur de transtypage
  // Objet monInstance = 5;   // IDEM
  Objet monInstance; // appel implicite du constructeur par défaut
  monInstance = 5; // affectation avec transtypage automatique
  Serial.println(monInstance.voirLeChiffre());

}

void loop() {
  // put your main code here, to run repeatedly:

}

ou le mieux :

class Objet
{
  public:
    double leChiffre;
    //Objet(); // constructeur par défaut
    Objet() = default;
    Objet(double c1); // constructeur de transtypage
    double voirLeChiffre();
};

//Objet::Objet() {}
Objet::Objet(double c1)
{
  leChiffre = c1;
}

double Objet::voirLeChiffre()
{
  return this->leChiffre;
}

void setup() {
  Serial.begin(115200);
  // Objet monInstance(5); //appel implicite du constructeur de transtypage
  // Objet monInstance = 5;   // IDEM
  Objet monInstance; // appel implicite du constructeur par défaut
  monInstance = 5; // affectation avec transtypage automatique
  Serial.println(monInstance.voirLeChiffre());

}

void loop() {
  // put your main code here, to run repeatedly:

}

==> Objet() = default;

Merci à tous les deux.
Bonne soirée à vous.

PS : Bon mais maintenant je change et je remplace dans mon langage transtypage par conversion !

On dirait plutôt "fonction membre" que "méthode de classe" et si vous voulez utiliser ce terme, utilisez plutôt "méthode d'instance" car le terme "méthode de classe" est souvent associé à une fonction que vous pouvez appeler sur la classe elle même, même sans instance (elles n'ont pas de this).

https://en.cppreference.com/w/cpp/language/static

Merci @J-M-L
Oui je ne l’avais même pas compris comme ça. Maintenant c’est bon j’utiliserai le bon terme et j’ai compris la différence entre fonction membre et méthode de classe :wink:

Merci

1 Like

Pourquoi obliger le compilateur à recréer un constructeur par défaut qui ne sert à rien

Objet monInstance(5); //appel implicite du constructeur de transtypage
Cette ligne est suffisante tu crées l'objet avec la valeur en paramètre, comme tu l'écris c'est un appel implicite du constructeur avec un argument
Faire
Objet monInstance = 5; // IDEM
Ce n'est pas idem et c'est la raison pour laquelle les enseignants font une différence entre un ou plusieurs paramètres. Dans ce cas le compilateur sait que le constructeur attend un seul paramètre il l'affecte donc au paramètre.
Ce n'est donc pas la bonne façon de faire puisque ton appel est explicite.
A noter en plus que tu passes d'un int à un double et que ton compilateur devrait te mettre un warning permissive

Les variables de classe sont toujours private et ce type de méthode qui sert à lire la valeur d'une variable s'appelle un accesseur ou getter et en général on la fait commencer par get et finir par const pour ne pas risquer de modifier la valeur lue.

double Objet::getLeChiffre()const
{
  return leChiffre;
}

J'ai testé toutes les possibilités pour mieux comprendre :wink:

Oui @le_viking je le sais,

Je pensais que c'était la même chose, tu dois avoir raison, je le voyez un peu comme ça mais dans le cadre de l'instanciation d'une classe :

int nb(4);
int nb2 = 5;
int nb3{6};
void setup() {
  Serial.begin(115200);

  Serial.println( nb);
  Serial.println( nb2);
  Serial.println( nb3);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Oui c'est parfaitement exact j'ai déjà utilisé des setter et des getter :wink:
Tu confirmes que :

Objet monInstance(5); 

et

Objet monInstance = 5;   // IDEM`

Ce n'est pas la même chose et le même résultat ?

Et puis ça je comprends pas :

PS : j'ai écrit mon code rapidement juste pour faire des tests mais tes remarques sont pertinentes.

Merci

C'est le même résultat mais obtenu d'une façon différente. C'est la raison pour laquelle on appelle un constructeur à un argument constructeur de transtypage parce que cette opération dite de transtypage est possible, tu affectes une valeur à un objet ce qui paraît au premier abord impossible. Cette syntaxe n'est pas utilisée en C++
c'est différent de int nb2 = 5; parce que là, tu affectes une valeur à une variable et que cette syntaxe C reste valide en C++ puisque C++ est compatible C

Oui, j'avais compris
Quand tu fais ceci
Objet::Objet(double c1)
puis cela
Objet monInstance(5)
le compilateur voit le 5 comme un int, il envoie donc un warning pour dire qu'il fait une conversion