lang:cpp:classes
Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente | ||
| lang:cpp:classes [2020/05/12 22:39] – Ajout de "Quelle "Rule of *" choisir ?" root | lang:cpp:classes [2025/07/28 13:38] (Version actuelle) – [Version "Rule of Zero"] : clarification du commentaire ''does not have a user-declared'' root | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| =====Sans héritage===== | =====Sans héritage===== | ||
| - | ====Multiples constructeurs==== | + | ====Version "Rule of Zero"==== |
| - | Il est possible d'appeler un constructeur depuis un autre constructeur. C'est pratique si on souhaite avoir deux constructeurs très proche. | + | |
| + | Cette règle s'applique lorsque la classe ne gère pas de ressources. | ||
| + | |||
| + | Il est aussi conseillé de réduire son utilisation aux classes n' | ||
| <code cpp> | <code cpp> | ||
| - | class A{}; | + | class Class |
| - | class B : public A | + | |
| { | { | ||
| | | ||
| - | // Call constructor B(int) from constructor B(). | + | // Constructor. |
| - | | + | |
| - | B() : B(5){} | + | // Pas de définition explicite du destructeur, |
| - | // Avoid default function argument | + | // ni de copy/move contructor/ |
| - | | + | } |
| - | + | ||
| - | | + | |
| - | int a_; | + | |
| - | }; | + | |
| </ | </ | ||
| + | |||
| + | Le post originel (?) sur la règle du zéro : < | ||
| + | |||
| + | < | ||
| + | If the definition of a class X does not explicitly declare a move constructor, | ||
| + | * X does not have a user-declared copy constructor, | ||
| + | * X does not have a user-declared copy assignment operator, | ||
| + | * X does not have a user-declared move assignment operator, and | ||
| + | * X does not have a user-declared destructor, | ||
| + | * the move constructor would not be implicitly defined as deleted. | ||
| + | < | ||
| + | |||
| + | '' | ||
| + | |||
| + | Autre explication : [[https:// | ||
| ====Version "Rule of five" | ====Version "Rule of five" | ||
| - | <WRAP center round important 60%> | + | Cette version |
| - | Considéré comme une mauvaise pratique. Utiliser la version | + | |
| - | </ | + | |
| <code cpp> | <code cpp> | ||
| Ligne 44: | Ligne 55: | ||
| } | } | ||
| </ | </ | ||
| - | L' | ||
| - | La règle | + | <WRAP center round info 60%> |
| + | Cette règle impose de définir ces 5 méthodes. Pour des raisons de simplicité lors de la déclaration initiale d'une classe, je déclare | ||
| - | [[https:// | + | L' |
| + | </ | ||
| + | |||
| + | <WRAP center round info 60%> | ||
| Il est préférable d' | Il est préférable d' | ||
| [[http:// | [[http:// | ||
| + | </ | ||
| - | ====Version "Rule of Zero"==== | + | ===Destructeur=== |
| + | |||
| + | Une classe doit avoir un destructeur virtuel si l' | ||
| + | |||
| + | L' | ||
| <code cpp> | <code cpp> | ||
| - | class Class | + | struct A |
| + | { | ||
| + | ~A() = default; | ||
| + | }; | ||
| + | |||
| + | struct B : public A | ||
| + | { | ||
| + | virtual ~B() {std::cout << " | ||
| + | }; | ||
| + | |||
| + | int main( int argc, char** argv ) | ||
| + | { | ||
| + | A* t = new B(); | ||
| + | // On n' | ||
| + | delete t; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Il faut de même utiliser '' | ||
| + | |||
| + | <code cpp> | ||
| + | class TestClass | ||
| { | { | ||
| | | ||
| - | | + | |
| - | | + | |
| + | }; | ||
| + | |||
| + | struct A | ||
| + | { | ||
| + | ~A() = default; | ||
| + | }; | ||
| + | |||
| + | struct B : public A | ||
| + | { | ||
| + | TestClass t; | ||
| + | }; | ||
| + | |||
| + | int main( int argc, char** argv ) | ||
| + | { | ||
| + | A* a = new B(); | ||
| + | // Le destructeur de TestClass de B n'est pas appelé. | ||
| + | delete a; | ||
| } | } | ||
| + | |||
| </ | </ | ||
| - | Le post originel (?) sur la règle du zéro : < | + | Sortie sans le destructeur de '' |
| - | C'est la version idéale et ça devrait être le compartiment par défaut. S'il est nécessaire de mettre des fonctions dans le destructeur, | + | < |
| + | const | ||
| + | </ | ||
| - | < | + | |
| - | If the definition of a class X does not explicitly declare a move constructor, | + | |
| - | | + | |
| - | * X does not have a user-declared copy assignment operator, | + | |
| - | * X does not have a user-declared move assignment operator, and | + | |
| - | * X does not have a user-declared destructor, | + | |
| - | * the move constructor would not be implicitly defined as deleted. | + | |
| - | < | + | |
| - | Par '' | + | Si c'est un objet explicitement déclaré, le destructeur sera appelé lors du bloc fermant. |
| - | ====Quelle "Rule of *" choisir ?==== | + | <code cpp> |
| + | int main() | ||
| + | { | ||
| + | A a; | ||
| + | { | ||
| + | B b; | ||
| + | // On appelle ~B() | ||
| + | } | ||
| + | // On appelle ~A() | ||
| + | } | ||
| + | </ | ||
| - | Dans tous les cas, il faut privilégier la version | + | La technique de mettre un bloc de crochet pour forcer un appel du destructeur à un moment précis est utilisé fréquemment par les '' |
| - | Si on définit l'un des 4 opérateurs | + | <code cpp> |
| + | int main() | ||
| + | { | ||
| + | std::mutex io_mutex; | ||
| + | { | ||
| + | std:: | ||
| + | std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std:: | ||
| + | // le mutex est automatiquement libéré lors de l' | ||
| + | } | ||
| + | return 1; | ||
| + | } | ||
| + | </ | ||
| - | Concernant le destructeur, | + | Si l'objet est temporaire, le destructeur |
| - | Si l' | + | <code cpp> |
| + | #include < | ||
| - | Mauvais | + | class A |
| + | { | ||
| + | public: | ||
| + | A() noexcept { std::cout << " | ||
| + | ~A() noexcept { std::cout << " | ||
| + | int foo() { return 1; } | ||
| + | }; | ||
| + | |||
| + | int foo2(int a) | ||
| + | { | ||
| + | std::cout << " | ||
| + | return a; | ||
| + | } | ||
| + | |||
| + | int main() | ||
| + | { | ||
| + | std::cout << " | ||
| + | foo2(foo2(A().foo())); | ||
| + | // Le destructeur de A est appelé ici. | ||
| + | std::cout << " | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Affichage : | ||
| + | |||
| + | < | ||
| + | 0 | ||
| + | A | ||
| + | int | ||
| + | int | ||
| + | ~A | ||
| + | 1 | ||
| + | </ | ||
| + | |||
| + | ===Les 4 opérateurs copy/move constructor/ | ||
| + | |||
| + | On part du principe que les 4 opérateurs sont toujours définis explicitement (y compris via '' | ||
| + | |||
| + | * Par défault, '' | ||
| + | |||
| + | Avec l' | ||
| + | |||
| + | <WRAP center round important 60%> | ||
| + | Contrairement aux classes qui définissent les opérateurs '' | ||
| + | </ | ||
| + | |||
| + | Exemple avec '' | ||
| <code cpp> | <code cpp> | ||
| Ligne 117: | Ligne 238: | ||
| </ | </ | ||
| - | Bon : | + | Exemple avec '' |
| <code cpp> | <code cpp> | ||
| Ligne 148: | Ligne 269: | ||
| TestClass t2 = std:: | TestClass t2 = std:: | ||
| } | } | ||
| + | </ | ||
| + | |||
| + | * Mettre les 2 '' | ||
| + | |||
| + | L' | ||
| + | |||
| + | Mauvais : | ||
| + | |||
| + | <code cpp> | ||
| + | class B { | ||
| + | | ||
| + | virtual char m() { return ' | ||
| + | }; | ||
| + | |||
| + | class D : public B { | ||
| + | | ||
| + | char m() override { return ' | ||
| + | }; | ||
| + | |||
| + | void f(B& b) { | ||
| + | auto b2 = b; // oops, slices the object; b2.m() will return ' | ||
| + | } | ||
| + | |||
| + | D d; | ||
| + | f(d); | ||
| + | </ | ||
| + | |||
| + | OK : | ||
| + | |||
| + | <code cpp> | ||
| + | #include < | ||
| + | |||
| + | class B { | ||
| + | | ||
| + | B() = default; | ||
| + | // Si on implémente ce clone, il faut mettre le constructeur par copie en public. | ||
| + | // Il est préférable de mettre ce constructeur en privée pour empêcher | ||
| + | // sa mauvaise utilisation. | ||
| + | virtual std:: | ||
| + | virtual char m() { return 1; } | ||
| + | virtual ~B() = default; | ||
| + | |||
| + | | ||
| + | // Peut être protected si clone est pure virtuelle. | ||
| + | B(const B&) = default; | ||
| + | B& operator=(const B&) = delete; | ||
| + | }; | ||
| + | |||
| + | class D : public B { | ||
| + | | ||
| + | D() = default; | ||
| + | D(const D&) = default; | ||
| + | D& operator=(const D&) = delete; | ||
| + | std:: | ||
| + | char m() override { return 10; } | ||
| + | virtual ~D() = default; | ||
| + | }; | ||
| + | |||
| + | char f(B& b) { | ||
| + | auto b2 = b.clone(); | ||
| + | return b2->m(); | ||
| + | } | ||
| + | |||
| + | int main() { | ||
| + | D d; | ||
| + | return f(d); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | * '' | ||
| + | |||
| + | Si on implémente l' | ||
| + | |||
| + | <code cpp> | ||
| + | class TestClass | ||
| + | { | ||
| + | | ||
| + | TestClass() {std::cout << " | ||
| + | TestClass(TestClass const& other) {std::cout << " | ||
| + | ~TestClass() {std::cout << " | ||
| + | }; | ||
| + | |||
| + | int main( int argc, char** argv ) | ||
| + | { | ||
| + | TestClass t; | ||
| + | TestClass t2 = std:: | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Sortie : | ||
| + | |||
| + | < | ||
| + | const | ||
| + | copy | ||
| + | destruct | ||
| + | destruct | ||
| </ | </ | ||
| Ligne 283: | Ligne 500: | ||
| } | } | ||
| </ | </ | ||
| + | |||
| + | ====vtable==== | ||
| + | |||
| + | Il n' | ||
| + | |||
| + | <code cpp> | ||
| + | #include < | ||
| + | |||
| + | struct S { | ||
| + | int x; | ||
| + | virtual void f() {} | ||
| + | }; | ||
| + | |||
| + | int main() { | ||
| + | S s; | ||
| + | s.x = 5; | ||
| + | std::cout << "size : " << sizeof(S) << " | ||
| + | void*** ptr = (void***)& | ||
| + | std::cout << " | ||
| + | std::cout << "bytes : " << *ptr << " and " << *(ptr + 1) << " | ||
| + | std::cout << " | ||
| + | << " | ||
| + | std:: | ||
| + | return 0; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | [[http:// | ||
| + | |||
| ====override / redéfinition==== | ====override / redéfinition==== | ||
| ===Cas général=== | ===Cas général=== | ||
| Ligne 318: | Ligne 564: | ||
| Lors de l' | Lors de l' | ||
| + | |||
| + | Il est possible d' | ||
| + | |||
| + | <code cpp> | ||
| + | class A{}; | ||
| + | class B : public A | ||
| + | { | ||
| + | | ||
| + | // Call constructor B(int) from constructor B(). | ||
| + | // No need (forbidden) to call A. | ||
| + | B() : B(5){} | ||
| + | // Avoid default function argument | ||
| + | B(int a/* = 5*/):A(), a_(a){} | ||
| + | |||
| + | | ||
| + | int a_; | ||
| + | }; | ||
| + | </ | ||
| * Destructeur | * Destructeur | ||
| Ligne 325: | Ligne 589: | ||
| Pour le destructeur, | Pour le destructeur, | ||
| + | Par défaut, un destructeur est '' | ||
| ====overload / surcharge==== | ====overload / surcharge==== | ||
| * Cas général | * Cas général | ||
| Ligne 408: | Ligne 673: | ||
| </ | </ | ||
| + | ====Interface==== | ||
| + | |||
| + | Il n'y a pas une unique méthode pour définir une interface. | ||
| + | |||
| + | ===Classe abstraite=== | ||
| + | |||
| + | Il faut faire une classe avec tous les prototypes et les déclarer toutes virtuelles pures. | ||
| + | |||
| + | '' | ||
| + | |||
| + | <code cpp> | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | struct BaseClass | ||
| + | { | ||
| + | virtual ~BaseClass() = default; | ||
| + | virtual std::string getName() const = 0; | ||
| + | }; | ||
| + | |||
| + | struct Bar : BaseClass | ||
| + | { | ||
| + | std::string getName() const override | ||
| + | { | ||
| + | return " | ||
| + | } | ||
| + | }; | ||
| + | |||
| + | struct Foo : BaseClass | ||
| + | { | ||
| + | std::string getName() const override | ||
| + | { | ||
| + | return " | ||
| + | } | ||
| + | }; | ||
| + | |||
| + | int main() | ||
| + | { | ||
| + | std:: | ||
| + | vec.emplace_back(std:: | ||
| + | vec.emplace_back(std:: | ||
| + | |||
| + | for (const auto& v : vec) std::cout << v-> | ||
| + | |||
| + | std::cout << std::endl; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===Template=== | ||
| + | |||
| + | '' | ||
| + | |||
| + | <WRAP center round important 60%> | ||
| + | Ce système de masquage ne doit être appliqué que si nécessaire. | ||
| + | |||
| + | [[https:// | ||
| + | </ | ||
| + | |||
| + | |||
| + | Ici, '' | ||
| + | |||
| + | <code cpp> | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | class Object | ||
| + | { | ||
| + | | ||
| + | template < | ||
| + | Object(std:: | ||
| + | |||
| + | std::string getName() const | ||
| + | { | ||
| + | return object-> | ||
| + | } | ||
| + | |||
| + | struct Concept | ||
| + | { | ||
| + | virtual ~Concept() {} | ||
| + | virtual std::string getName() const = 0; | ||
| + | }; | ||
| + | |||
| + | template< | ||
| + | struct Model final : Concept | ||
| + | { | ||
| + | Model(std:: | ||
| + | std::string getName() const final | ||
| + | { | ||
| + | return object-> | ||
| + | } | ||
| + | | ||
| + | std:: | ||
| + | }; | ||
| + | |||
| + | std:: | ||
| + | }; | ||
| + | |||
| + | struct Bar | ||
| + | { | ||
| + | std::string getName() const | ||
| + | { | ||
| + | return " | ||
| + | } | ||
| + | }; | ||
| + | |||
| + | struct Foo | ||
| + | { | ||
| + | std::string getName() const | ||
| + | { | ||
| + | return " | ||
| + | } | ||
| + | }; | ||
| + | |||
| + | int main() | ||
| + | { | ||
| + | std:: | ||
| + | vec.emplace_back(std:: | ||
| + | vec.emplace_back(std:: | ||
| + | |||
| + | for (const auto& v : vec) | ||
| + | std::cout << v-> | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | [[https:// | ||
| =====Hack===== | =====Hack===== | ||
lang/cpp/classes.1589315950.txt.gz · Dernière modification : de root
