Skip to content

Instantly share code, notes, and snippets.

@BruJu
Last active July 12, 2022 19:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BruJu/2b27d281cbde777f60b620a125d08819 to your computer and use it in GitHub Desktop.
Save BruJu/2b27d281cbde777f60b620a125d08819 to your computer and use it in GitHub Desktop.
C++ Lisibility Tips

Comment avoir 8/6 à la partie programmation du projet de Génie Log

(quand c'est moi qui note)

Ne pas utiliser de variables globales

Les variables globales nuisent à la lisibilité du code. S'il y a des variables globales, les entrées de vos fonctions peuvent être n'importe quoi : on ne sait pas si des valeurs issues de variables globales seront utilisées en entrées ou pas.

Si toutes les données sont accessibles par variable globale, alors on ne sait rien du tout des entrées de vos fonctions.

Si vous n'avez pas de variable globale, on voit dès la signature de la fonction quelles sont les entrées :

std::vector<Animal> animaux;
std::vector<Gerant> gerants;

std::string getNom(std::string id);
// A priori, cette fonction va utiliser une variable globale. Peut-être
// que ça va la modifier ? J'en sais rien.
std::string getNom(std::string id, const std::vector<Animal> & animaux);
// Ok

Ecrivez const

void foo(const Animal animal) { // Ok la valeur de animal n'est pas modifiée
  const std::string name = animal.getName(); // ok le nom ne changera pas
  // ...

}

Faites des passages par référence

size_t count_tigers(std::vector<Animal> animals) {
  // S'il y a 10000 animaux, on viens de copier 10000 animaux
  size_t count = 0;
  for (Animal animal : animals)  { // Et on copie encore une fois chaque animal
    if (animal.getEspece() == "Tiger") {
      ++count;
    }
  }
  return count;
}
size_t count_tigers(const std::vector<Animal> & animals) { // ok on ne recopie pas
  size_t count = 0;
  for (const Animal & animal : animals)  { // Ok on ne recopie pas
    if (animal.getEspece() == "Tiger") {
      ++count;
    }
  }
  return count;
}

Pas la peine d'en faire autant

Lorsque vous écrivez une classe, par défaut, le compilateur vous crée :

  • Un constructeur par copie qui consiste à recopier tous les membres
  • Un opérateur d'affectation par copie qui consiste à recopier tous les membres
  • Un destructeur qui consiste à détruire tous les membres

Si vous n'avez pas de pointeur possédés (T * remplis avec new), et pas de ressource particulière allouée explicitement (comme des FILE *), et pas d'héritge, vous n'avez surement pas définir tout ça :

// .h

class Animal {
  public:
    Animal(std::string nom, std::string espece, int taille, int age);
    Animal(const Animal &);
    Animal= operator=(const Animal &);
    ~Animal();
    
    std::string getName();
    std::string getEspece();
    int getTaille();
    int getAge();

  private:
    std::string name;
    std::string espece;
    int taille;
    int age;
};

// .cpp

Animal::Animal(std::string nom, std::string espece, int taille, int age)
 : nom(nom), espece(espece), taille(taille), age(age) {
}

Animal::Animal(const Animal & other)
  : nom(other.nom),
  espece(other.espece),
  taille(other.taille),
  age(other.age)
  {
}

Animal & Animal::operator=(const Animal & other) {
  if (this == &other) return *this;
  nom = other.nom;
  espece = other.espece;
  taille = other.taille;
  age = other.age;
  return *this;
}

~Animal() {

}

std::string Animal::getName() {
  return name;
}

std::string Animal::getEspece() {
  return espece;
}

int Animal::getTaille() {
  return taille;
}

int getAge() {
  return age;
}

Le même code en plus court :

// .h

class Animal {
  public:
    Animal(std::string nom, std::string espece, int taille, int age);
    
    std::string getName() const { return name; }
    std::string getEspece() const { return espece; }
    int getTaille() const { return taille; }
    int getAge() const { return age; }

  private:
    std::string name;
    std::string espece;
    int taille;
    int age;
};

// .cpp

Animal::Animal(std::string nom, std::string espece, int taille, int age)
 : nom(nom), espece(espece), taille(taille), age(age) {
}

(La communauté préfère d'ailleurs la seconde version qui suit le "Rule of 0" : https://en.cppreference.com/w/cpp/language/rule_of_three)

N'écrivez pas le type des itérateurs

On vous a surement appris :

size_t sum_ages(const std::vector<Animal> & animals) {
  int sum = 0;
  for (std::vector<Animal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
    sum += it->getAge();
  }
  return sum;
}

Le type std::vector<Animal>::const_iterator est long à écrire, long à lire, et est peu informatif. Ecrivez auto à la place.

size_t sum_ages(const std::vector<Animal> & animals) {
  int sum = 0;
  for (auto it = animals.begin(); it != animals.end(); ++it) {
    sum += it->getAge();
  }
  return sum;
}

Ou mieux : n'écrivez pas d'itérateur et utilisez une Range-based for loop :

size_t sum_ages(const std::vector<Animal> & animals) {
  int sum = 0;
  for (const Animal & animal : animals) {
    sum += animal.getAge();
  }
  return sum;
}
@pedalpoweredsoftware
Copy link

pedalpoweredsoftware commented Jul 5, 2022

Please pardon my English. Also, I haven't written C++ since 95 (switched to Java, supplemented with other langs since), so perhaps there's something new I'm somehow unaware of. For the last example, shouldn't "it->getAge()" be "animal->getAge()"?

@BruJu
Copy link
Author

BruJu commented Jul 8, 2022

Yes you are right. It is actually animal.getAge(). I fixed it. Thank you.

@pedalpoweredsoftware
Copy link

Right! animal.getAge(), as with Java. Been 27 years since I coded C++. ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment