Eviter des copies
A la construction d’un objet
A la construction d’un objet, les champs s’initialisent tout seul avant le constructeur. Quand on ne dit sur la création d’un objet, C++ appelle le constructeur par défaut qui n’a pas de paramètres.
#include <iostream>
#include <vector>
class Point {
public:
int x;
int y;
Point() {
std::cout << "Creation par defaut d'un point\n";
x = 0; y = 0;
}
Point(int ax, int ay) {
x = ax; y = ay;
std::cout << "Creation d'un point\n";
}
};
class Animal {
public:
Point pos; // malheureusement est initialisé...
Animal(Point apos) {
pos = apos;
};
};
int main()
{
Animal a(Point(5, 2));
std::cout << a.pos.x;
return 0;
}
Pour éviter un appel au constructeur par défaut et appeler uniquement les constructeurs pour les champs au même moment du constructeur de l’animal, on utilise une liste d’initialisation.
#include <iostream>
#include <vector>
class Point {
public:
int x;
int y;
Point() {
std::cout << "Creation par defaut d'un point\n";
x = 0; y = 0;
}
Point(int ax, int ay) {
x = ax; y = ay;
std::cout << "Creation d'un point\n";
}
};
class Animal {
public:
Point pos;
Animal(Point apos) pos(apos) {}; // pos n'est plus initialement avec le constructeur par défaut car il apparaît dans la liste d'initialisation
};
int main()
{
Animal a(Point(5, 2));
std::cout << a.pos.x;
return 0;
}
Et puis tant qu’à faire, autant avoir des listes d’initialisation partout.
#include <iostream>
#include <vector>
class Point {
public:
int x;
int y;
Point() : x(0), y(0) {
std::cout << "Creation par defaut d'un point\n";
}
Point(int ax, int ay) : x(ax), y(ay) {
std::cout << "Creation d'un point\n";
}
};
class Animal {
public:
Point pos;
Animal(Point apos) : pos(apos) {}; // pos n'est plus initialement avec le constructeur par défaut car il apparaît dans la liste d'initialisation
};
int main()
{
Animal a(Point(5, 2));
std::cout << a.pos.x;
return 0;
}
Élision de copie
Une élision de copie est une omission de copie quand C++ sait qu’elle n’est pas nécessaire : typiquement lors d’un appel ou au retour d’une fonction. On évite d’appeler un constructeur de copie inutilement.
Exemple
Considérons ce morceau de code.
V.push_back(getStringFromClient())
Voici ce qui se passera sans élision de copie :
getStringFromClient()renvoie un objetstringque l’on note temporairement A (attention, il n’y a pas de variable A dans le programme, ce n’est donc pas une l-value)- On crée une copie A’ que l’on donne à
push_back - Puis A est supprimé
Mais il y a trop de copie. Au lieu de cela, les copies sont évitées, et les données sont juste transférées de getStringFromClient à l’intérieur de V.push_back(...).
Attention, le compilateur fait une sorte de move semantics automatique dans ce cas là. On appelle ça une élision de copie.
Exercices
- Voici un programme. Que fait-il ?
#include<iostream>
class Cat {
public:
Cat() {
std::cout << "By default constructor\n";
}
Cat(bool is_magical) {
std::cout << "Constructor\n";
}
Cat(Cat& cat) {
std::cout << "Constructor by copy\n";
}
Cat(Cat&& cat) {
std::cout << "Constructor by move\n";
}
};
Cat getSuperCat() {
return Cat();
}
void teachSth(Cat c) {}
int main()
{
Cat c;
teachSth(c);
}
Il appelle le constructeur par défaut, puis le constructeur par copie.
Voici un programme. Que fait-il ?
#include<iostream>
class Cat {
public:
Cat() {
std::cout << "By default constructor\n";
}
Cat(bool is_magical) {
std::cout << "Constructor\n";
}
Cat(Cat& cat) {
std::cout << "Constructor by copy\n";
}
Cat(Cat&& cat) {
std::cout << "Constructor by move\n";
}
};
Cat getSuperCat() {
return Cat();
}
void teachSth(Cat c) {}
int main()
{
teachSth(getSuperCat());
}
Il appelle le constructeur par défaut.
Et que fait ce programme ?
#include<iostream>
class Cat {
public:
Cat() {
std::cout << "By default constructor\n";
}
Cat(bool is_magical) {
std::cout << "Constructor\n";
}
Cat(Cat& cat) {
std::cout << "Constructor by copy\n";
}
Cat(Cat&& cat) {
std::cout << "Constructor by move\n";
}
};
Cat getSuperCat() {
Cat c;
return c;
}
void teachSth(Cat c) {}
int main()
{
teachSth(getSuperCat());
}
Il appelle le constructeur par défaut.