Xavier Lamorlette
Sommaire :
Implémentation de l'assignation par copie (copy assignment) par un passage de paramètre par valeur,
qui appelle le constructeur par copie (copy constructor), puis par l'appel à une méthode swap()
,
qui échange les zones mémoires, sur ce paramètre.
Cette méthode swap()
peut être utilisée pour le constructeur par mouvement (move constructor).
class A {
A() {…}
A(const A &) {…}
A & operator =(A other) {
swap(other);
return * this;
}
A(A && other):
A() {
swap(other);
}
void swap(A & other) {…}
};
On ne peut pas utiliser le polymorphisme par héritage dans la résolution des surcharges de fonctions. Le code suivant ne fonctionne pas comme on pourrait l'espérer, car la résolution des surcharges est faite à la compilation :
class A {…};
class B: A {…};
void foo(A &) {}
void foo(B &) {}
{
B b;
A & a = b;
foo(a);
}
Il faut utiliser le “double dispatch” :
class A {
virtual void call_foo() {
foo(* this);
}
};
class B: A {
void call_foo() override {
foo(* this);
}
};
void foo(A &) {…}
void foo(B &) {…}
{
B b;
A & a = b;
a.call_foo();
}
Si foo
est une classe, c'est le design pattern Visitor.
D'une manière plus générale, le multiple dispatch est le polymorphisme simultané sur plusieurs objets.
class Proxy {
public:
Proxy():
impl(std::make_unique<Real_object>()) {
}
Real_object * operator->() {
return impl;
}
private:
std::unique_ptr<Real_object> impl;
};
Ceci peut permettre d'étendre une classe, en faisant une sorte de design pattern Decorator :
class A {
public:
int x;
};
class A_extended {
public:
A_extended(A & a):
a(a) {
}
A * operator -> () {
return & a;
}
int sum(int y) const {
return a.x + y;
}
private:
A & a;
};
{
A a{12};
A_extended a_extended(a);
std::cout << a_extended->x << std::endl;
std::cout << a_extended.sum(3) << std::endl;
}
Pour effacer efficacement plusieurs éléments d'un vecteur :
std::remove
(ou std::remove_if
qui prend en paramètre un prédicat) déplace les éléments à la fin du vecteur et renvoie le nouvel itérateur de fin ;std::vector<>::erase
efface les éléments de la fin du vecteur.#include <algorithm>
#include <vector>
{
std::vector<T> my_vector;
T value;
my_vector.erase(std::remove(my_vector.begin(), my_vector.end(), value), my_vector.end());
}
Ceci n'est plus nécessaire en C++20 grâce au nouvel algorithme std::erase
(et aussi std::erase_if
).
Une classe mixin fournit des méthodes à d'autres classes sans en être parente.
En C++ : classe template qui hérite de la classe paramêtre template.
Le mixin est aussi appelé “Curiously Recurring Template Pattern”, “F-bound polymorphism”, ou “upside-down inheritance”.
Cela permet notamment d'implémenter :
class Object {
virtual void operation();
};
template <typename Base>
class Decoration: public Base {
using Base::Base;
void operation() override {
Base::operation();
added_operation();
}
};
{
Decoration<Object>().operation();
}
template <typename T>
class Base {
void interface() {
static_cast<T *>(this)->implementation();
}
static void Function() {
T::Sub_function();
}
};
class Derived: Base<Derived> {
void implementation();
static void Sub_function();
};
class Base {
virtual Base * clone() const = 0;
};
template <typename Derived>
class Clonable_base: public Base {
Base * clone() const override {
return new Derived(static_cast<Derived const &>(* this));
}
};
class Derived: public Clonable_base<Base> {
};
template <typename T>
class Comparable {
friend bool operator==(const T & lhs,
const T & rhs) {
return lhs.equal_to(rhs);
}
};
class Derived: Comparable<Derived> {
bool equal_to(const Derived & rhs) const {
…
}
};
Pour empêcher une mauvaise utilisation du mixin (empêcher de faire class Derived: Base<Other>
),
on peut rendre le constructeur de Base
privé :
template <typename T>
class Base {
public:
void interface() {
static_cast<T *>(this)->implementation();
}
private:
Base() = default;
friend T;
};
class Derived: Base<Derived> {
void implementation();
};
Cas particulier du design pattern Bridge.
“Pointer to implementation”, “compilation firewall” : cache les détails d'implémentation d'une classe dans une sous-classe dédiée que l'on accède via un “opaque pointer”.
Cela permet de :
Choix de conception : si on ne met dans la classe d'implémentation que les membres et méthodes privés, en laissant ceux publics dans la classe publique, on peut avoir des problèmes pour appeler des méthodes publiques depuis la classe d'implémentation. Par exemple, il faut rajouter dans la classe d'implémentation un pointeur vers la classe publique.
// A.hpp
class A {
public:
A();
~A();
private:
class Impl;
std::unique_ptr<Impl> impl;
};
// A.cpp
class A::Impl {
…
};
A::A():
impl(std::make_unique<Impl>()) {
}
A::~A() = default;
RAII: Resource Acquisition Is Initialisation: utilisation du destructeur, dont l'appel est garanti même en cas d'exception, pour libérer une ressource à la fin de la portée de l'instance. La ressource est un invariant de classe lié à son existence.
Note : lorsqu'il y a plusieurs ressources à gérer, les variables locales sont détruites dans l'ordre inverse de leur construction.
Ceci est utilisé par les std::shared_ptr
et les std::unique_ptr
.
Son utilisation est fortement recommandée pour le contrôle des mutex, des allocations dynamiques de mémoire, des fichiers et des sockets.
La dernière mise à jour de cette page date de mars 2022.
Le contenu de ce site est, en tant qu'œuvre originale de l'esprit, protégé par le droit d'auteur.
Pour tout commentaire, vous pouvez m'écrire à xavier.lamorlette@gmail.com.