Ecriture de services en C++11, Partie 2/2

Introduction

Voici la seconde partie de l’article consacrée à l’écriture de services en C++ dans l’esprit de la programmation par concept. Lors de la publication de la première partie, la seconde était déjà écrite, cependant j’ai pris du temps pour relire : paradoxalement cette partie est plus courte mais est aussi celle où j’ai eu le plus de mal à décider quoi aborder et quoi passer sous silence.

La première partie concerne les services spécifiques à un classe template, c’est à dire lorsque le concept se traduit par « la classe une instance de tel modèle ». La seconde partie traite de quelques formes idiomatiques des services spéciaux du C++, c’est à dire ceux qui ont une signification propre au langage.

Services spécifiques

Dans cette partie on va implémenter des services en considérant que les paramètres respectent le concept d’être une instance de la classe Box qui est maintenant template :

template<
    class
>
struct Box;
Box est maintenant template

Pour commencer on va reprendre le service print_number :

#include<iostream>

template<
    class Object
>
void print_number(
    const Box<Object>& box
)
{
    std::cout   << "La boite contient "
        << box.open()
        << " objets distincts."
        << std::endl;
}

Et reprenons aussi le service duplicate :

#include<utility>

template<
    class Object
>
void duplicate(
    Box<Object>& box
)
{
    print_number(box);
    box.clone_all();
    print_numer(box);   
}

template<
    class Object
>
void duplicate(
    Box<Object>&& box
)
{
    print_number(box);
    box.clone_all();
    print_numer(
        std::move(box)
    );  
}

La différence entre le premier et le second service est le fait que l’on modifie le paramètre. Lorsque le paramètre n’est pas modifié, le paramètre peut être passé en utilisant une référence constante et une seule implémentation du service suffit. Dans le cas contraire, il y a deux implémentations, une avec un paramètre passé par lvalue référence et une avec un paramètre passé par rvalue référence. Dans le cas de deux versions, au sein de celle avec une rvalue référence, le paramètre est passé en utilisant std::move au dernier service utilisé si celui-ci n’est pas membre, de la même manière qu’on avait utilisé std::forward.

De manière similaire à la partie précédente, traitons l’instruction result en réimplémentant le service extract_rand. Le service ne modifie pas le paramètre, on n’a donc qu’une implémentation à fournir. On va considérer qu’une instance de la classe template Box a une sémantique d’entité, elle ne peut donc pas être copiée, on va cependant supposer qu’elle dispose de la sémantique de déplacement1 :

template<
    class Object
>
Box<Object> extract_rand(
    const Box<Object>& box
)
{       
    Box<Object> result;
    //algorithme
    return result;
}
On utilise un objet local sur lequel on travaille, ensuite on le retourne.

Maintenant que l’on dispose de notre nouvelle version de extract_rand, l’on va pouvoir réimplémenter notre service shake. Ce service illustre un service qui n’est utile que si le paramètre existe encore après l’exécution, il n’y a donc aussi besoin que d’une seule version :

#include<utility>

template<
    class Object
>
void shake(
    Box<Object>& box
)
{       
    Box<Object> extracted(
        extract_rand(box)
    );
    extracted.expend();
    box=std::move(extracted);
}
On travaille sur un objet local, ceci assure la résistance forte aux exceptions, ensuite on l’assigne au paramètre. L’utilisation de std::move permet d’indiquer que l’on n’a plus besoin de cet objet local.

Comparé à la situation précédente, on voit l’utilisation de std::move à la place de swap, qui n’existe pas dans le cas d’une sémantique d’entité. A nouveau on peut noter l’importance de la résistance forte aux exceptions.

Services spéciaux

Il existe différents services spéciaux en C++ : constructeur, destructeur et opérateurs d’affectation entre autres. Les seuls qui ont des implémentations idiomatiques sont les opérateurs d’affection. Il y a d’un part ceux liés à la sémantique de copie et ceux liés à la sémantique de déplacement2. Et d’autre part ceux spécifiques à une classe et ceux liées à toutes les instances d’une classe template.

Premièrement voyons l’opérateur d’affectation par déplacement spécifique à la classe :

template<
    class
>
struct Box
{
    Box& operator=(Box&& rhs)
    {
        swap(rhs);      
        return *this;
    }
};
Ceci suppose l’existence d’une fonction membre swap. Cette supposition n’est pas très contraignante, son existence est idiomatique.

Ensuite voyons l’opérateur d’affectation par copie spécifique à la classe :

#include<utility>

template<
    class
>
struct Box
{
    Box& operator=(const Box& rhs)
    {
        return operator=(
            Box(lhs)
        );
    }
};

Il y a un lien entre les deux implémentations, il montre clairement que le travail effectué est le même, la différence étant un éventuellement traitement du paramètres.

Voyons maintenant les opérateurs d’affectation généralisées, ils servent lors de l’affectation entre deux objets de type issus de la même classe template. Cependant ils ne remplacent pas ceux spécifiques à la classe. D’abord l’opérateur d’affectation généralisée par déplacement :

#include<utility>

template<
    class
>
struct Box
{
    template<class Object>
    Box& operator=(Box<Object>&& rhs)
    {   
        return operator=(
            Box(std::move(rhs))
        );
    }
};
On retrouve à nouveau l’utilisation d’un move pour indiquer que l’on a plus besoin du paramètre.

Ensuite voyons l’opérateur d’affectation généralisée par copie :

template<
    class
>
struct Box
{
    template<class Object>
    Box& operator=(const Box<Object>& rhs)
    {
        return operator=(
            Box(rhs)
        );
    }
};

On voit qu’il n’y a plus de lien entre les deux versions, cependant il y a un lien entre les spécifiques et les généralisées au travers des constructeurs de copie généralisées (ils n’ont pas de formes idiomatiques, ils ne seront donc pas présentés).

Conclusion

Cet article touche à sa fin. J’espère que la présentation est claire et le fond intéressant à lire. Comme je le disais en introduction, les éléments présentés n’ont pas pour prétention d’être une vérité incontestable et au contraire toute critique est la bienvenue.

Cet article ne se suffit pas à lui même, et des connaissances et expériences sur l’utilisation des concepts et de la résistance forte aux exceptions sont nécessaires pour saisir l’intérêt de certaines constructions présentées ici.

Remerciement

Je remercie les membres du chat de developpez.com (en particulier Winjerome, Linunix et gbdivers) qui ont relu techniquement et orthographiquement cet article (les deux parties).


Notes:

1 C’est possible si elle n’est pas destiné à être hérité publiquement, dans le cas contraire une manipulation par pointeur est possible avec un pattern clone.

2 Pour rappel on considère que les éléments proposant la sémantique de copie sont lourds à copier. Ainsi implémenter la sémantique de copie implique d’implémenter celle de déplacement.

Publicités

Une réflexion au sujet de « Ecriture de services en C++11, Partie 2/2 »

  1. Ping : C++ et Haskell – les concepts | C++, Qt, OpenGL, CUDA

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s