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 01:11] – [Conseils sur l'héritage] : ajout d'un exemple d'héritage root | lang:cpp:classes [2022/07/04 10:05] (Version actuelle) – [Destructeur] : lock_guard->scoped_lock root | ||
---|---|---|---|
Ligne 3: | Ligne 3: | ||
====Multiples constructeurs==== | ====Multiples constructeurs==== | ||
Il est possible d' | Il est possible d' | ||
+ | |||
+ | Bien qu'on soit dans le paragraphe sans héritage, j'ai mis un cas avec héritage pour montrer qu'on a pas besoin d' | ||
<code cpp> | <code cpp> | ||
Ligne 10: | Ligne 12: | ||
| | ||
// Call constructor B(int) from constructor B(). | // Call constructor B(int) from constructor B(). | ||
- | // Dont call the parent A() when calling another constructor of the same class. | + | // No need (forbidden) to call A. |
B() : B(5){} | B() : B(5){} | ||
// Avoid default function argument | // Avoid default function argument | ||
Ligne 19: | Ligne 21: | ||
}; | }; | ||
</ | </ | ||
+ | |||
+ | ====Version "Rule of Zero" | ||
+ | |||
+ | Cette règle s' | ||
+ | |||
+ | Il est aussi conseillé de réduire son utilisation aux classes n' | ||
+ | |||
+ | <code cpp> | ||
+ | class Class | ||
+ | { | ||
+ | | ||
+ | // Constructor. | ||
+ | Class() = default; | ||
+ | // Pas de destructeur. Pas de copy/move contructor/ | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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. | ||
+ | < | ||
+ | |||
+ | Par '' | ||
+ | |||
+ | 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 75: | ||
} | } | ||
</ | </ | ||
- | 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 destruteur 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. |
+ | |||
+ | <code cpp> | ||
+ | int main() | ||
+ | { | ||
+ | A a; | ||
+ | { | ||
+ | B b; | ||
+ | // On appelle ~B() | ||
+ | } | ||
+ | // On appelle ~A() | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | La technique de mettre un bloc de crochet pour forcer un appel du destructeur à un moment précis est utilisé fréquemment par les '' | ||
+ | |||
+ | <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; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Si l' | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | |||
+ | class A | ||
+ | { | ||
+ | public: | ||
+ | A() noexcept { std::cout << " | ||
+ | ~A() noexcept { std::cout << " | ||
+ | int foo() { return 1; } | ||
+ | }; | ||
+ | |||
+ | int foo2(int | ||
+ | { | ||
+ | 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> | ||
+ | #include < | ||
+ | |||
+ | class TestClass | ||
+ | { | ||
+ | | ||
+ | TestClass() {a_ = new int[1];} | ||
+ | TestClass(TestClass const& other) = delete; | ||
+ | TestClass(TestClass && other) noexcept = default; | ||
+ | TestClass& | ||
+ | TestClass& | ||
+ | ~TestClass() {delete[] a_;} | ||
+ | | ||
+ | int* a_ = nullptr; | ||
+ | }; | ||
+ | |||
+ | int main( int argc, char** argv ) | ||
+ | { | ||
+ | TestClass t; | ||
+ | TestClass t2 = std:: | ||
+ | // Après std::move, t.a_ vaut toujours la valeur d'origine car le constructeur copy vaut default. | ||
+ | // Il y a donc un double delete. | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Exemple avec '' | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | class TestClass | ||
+ | { | ||
+ | | ||
+ | TestClass() {a_ = new int[1];} | ||
+ | TestClass(TestClass const& other) = delete; | ||
+ | TestClass(TestClass && other) noexcept | ||
+ | { | ||
+ | this->a_ = std:: | ||
+ | } | ||
+ | TestClass& | ||
+ | TestClass& | ||
+ | { | ||
+ | this->a_ = std:: | ||
+ | return *this; | ||
+ | } | ||
+ | ~TestClass() {delete[] a_;} | ||
+ | | ||
+ | int* a_ = nullptr; | ||
+ | }; | ||
+ | |||
+ | int main( int argc, char** argv ) | ||
+ | { | ||
+ | TestClass t; | ||
+ | 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-> | ||
+ | } | ||
+ | |||
+ | 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 | ||
+ | </ | ||
====Contenu==== | ====Contenu==== | ||
Ligne 213: | Ligne 520: | ||
} | } | ||
</ | </ | ||
+ | |||
+ | ====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 255: | Ligne 591: | ||
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 338: | Ligne 675: | ||
</ | </ | ||
+ | ====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.1589238665.txt.gz · Dernière modification : 2020/05/12 01:11 de root