Xavier Lamorlette

C++ : idiomes

Copy and Swap

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) {…}
};

Double Dispatch

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.

“« En masse » delegation”

class Proxy {
public:
    Proxy():
        impl(std::make_unique<Real_object>()) {
    }
    Real_object * operator->() {
        return impl;
    }
private:
    std::unique_ptr<Real_object> impl;
};

Erase - Remove

Pour effacer efficacement plusieurs éléments d'un 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());
}

Mixin

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 :

PImpl

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”.

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

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.

Template Adapter

Classe template utilisée comme paramètre template.

Exemple :

template<typename T,
    template<typename> class Container_type = std::list>
class Stack {
public:
    void push(const T & element) {
        container.push_front(element);
    }

private:
    Container_type<T> container;
};

Template Function Object

Permet de simuler une lambda polymorphe :

struct get_foo {
    template <typename T>
    const string & operator() (const T & x) const {
        return x.foo;
    }
};

Extraction d'une lambda

Au lieu de :

Parameter parameter;
std::vector<A> as;
std::copy_if(as.begin(), as.end(), std::back_inserter(as),
    [& parameter](const A & a) {
        […]
        return result;
    });
on peut écrire :
auto predicate(const Parameter & parameter) {
    return [& parameter](const A & a) {
        […]
        return result;
    });
}

Parameter parameter;
std::vector<A> as;
std::copy_if(as.begin(), as.end(), std::back_inserter(as), predicate(parameter));

La dernière mise à jour de cette page date de juin 2020.

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.