User Tools

Site Tools


in204:tds:sujets:td1:part2

Partie II – Constructeurs en C++

TD1

Question n° 1

Nous considérons les deux fonctions suivantes (C++):

void myfunctionA()
{
  MyCounter counter;
  std::cout << counter.getCounter() << std::endl;
  std::cout << counter.getMax() << std::endl;
}
 
void myfunctionB() 
{
  MyCounter* counter = new MyCounter();
  std::cout << counter->getCounter() << std::endl;
  std::cout << counter->getMax() << std::endl;
  delete counter;
}

Exécuter les deux fonctions et expliquer pourquoi la valeur des champs est différente dans le cas où les objets sont alloués sur la pile ou dans le cas où sont alloués sur le tas.

Correction

Correction

Dans la fonction myfunctionA() l'objet est alloué sur la pile. Dans le cas d'une allocation sur la pile, la mémoire est obtenue en simplement retournant l'addresse mémoire courante du pointeur de pile et en incrémentant ensuite ce pointeur de la taille de l'objet.

 
    @MyCounter <= StackPointer
    StackPointer <= StackPointer + sizeof(MyCounter);

Dans la fonction myfunctionB() l'objet est alloué sur le tats. Dans le cas d'une allocation sur le tas, la mémoire est obtenue par un appel à une fonction d'allocation comme par exemple malloc qui repose sur un à une fonction offerte par le système d'exploitation.

 
    @MyCounter <= malloc(sizeof(MyCounter));

Même si a priori la documentation de malloc n'impose pas que la mémoire soit mise à zéro, la plupart des fonctions d'allocation de mémoire des systèmes d'exploitation mettent la mémoire à zéro pour éviter des problèmes de sécurité (ie. que l'on puisse retrouver des données manipulées par un autre programme et/ou un autre utilisateur et qui pourraient être confidentielles).

Dans le premier cas, nous comprenons que la valeur des champs count et max correspond aux valeurs présentes dans la pile. Dans le second cas, les valeurs des champs count et max sont a priori à zéro.

Il faut bien comprendre que si nous n'intialisons pas les champs de manière explicie, ceux-ci prendront la valeur contenue dans la mémoire, ce qui n'est que rarement ce que l'on souhaite.

Au final une bonne règle consiste systématiquement d'initialiser l'ensemble des champs définis dans un objet au moment de sa création.

Question n° 2

Que faut-il ajouter à la classe pour garantir que le comportement soit toujours correct ?

Correction

Correction

Il est nécessaire d'ajouter un constructeur par défaut qui initialise les champs de l'objet avec la valeur par défaut, soit 0 dans notre cas.

struct MyCounter
{
    public: 
       MyCounter(): counter(0), max(0)
       {}
};

Question n° 3

Dans la fonction useObjectA(), le programme initialise les différents champs des compteurs. Ceci n’est pas très élégant.

Proposer une solution pour créer des compteurs en passant en paramètre le nombre maximal d’éléments comptés à partir duquel le compteur revient à 0.

Simplifier le code de la fonction useObjectA() une fois que vous avez proposé ces solutions.

Correction

Correction

Il est possible d'ajouter des constructeurs spécialisés à la classe MyCounter, nous proposons les deux constructeurs suivants :

struct MyCounter
{
...
    explicit MyCounter(uint theMaxValue):
        counter(0), max(theMaxValue) 
    {}
    MyCounter(uint theCounter, uint theMaxValue): 
        counter(theCounter), max(theMaxValue)
    {}
};

Depuis la version C++11, il est possible de faire appel à un constructeur déjà défini. Ainsi, le code peut se simplifier comme suit :

struct MyCounter
{
...
    explicit MyCounter(uint theMaxValue): 
        MyCounter((uint)0, theMaxValue)
        // Effectue un appel au constructeur MyCounter(uint, uint)
    {}
    MyCounter(uint theCounter, uint theMaxValue): 
        counter(theCounter), max(theMaxValue)
    {}
};

La fonction useObjectA() peut désormais s'écrire comme suit:

void useObjectA() {
    MyCounter Counter1(2);
    MyCounter Counter2(4)
    for(unsigned i = 0; i <= 5; i++) {
      std::cout 
	<< "Valeur des compteurs (" 	<< Counter1.counter 
	<< ", " << Counter2.counter 	<< ")" << std::endl;
        Counter1.increment();
        Counter2.increment();
    }
}

Question n° 4

Proposer enfin un constructeur de recopie qui permet de créer un nouveau compteur étant la copie d’un compteur passé en paramètre.

Correction

Correction

Le constructeur de recopie se définit simplement comme suit :

struct MyCounter
{
    MyCounter(const MyCounter& anotherCounter):
        counter(anotherCounter.counter),
        max(anotherCounter.max)
    {}
};

in204/tds/sujets/td1/part2.txt · Last modified: 2022/11/18 10:50 (external edit)