RVO démystifié

Comment renvoie t-on une valeur depuis une fonction ? Pour cela un peu d’histoire, faisons appel au grand Pascal! Prenons la définition d’une fonction calculant le plus grand de deux entiers :

function max(a, b: integer) : integer;
begin
  if (a>b) then
    max := a
  else
    max := b;
end;

L’idée de Pascal est que l’identificateur de la fonction max est utilisé à la fois comme identificateur pour l’appel mais en interne comme identificateur permettant de stocker la valeur de retour. Autrement dit Pascal explicite l’existence d’un espace permettant de stocker la valeur de retour. On notera qu’il est aussi possible d’utiliser les procédures avec un paramètre passé par référence :

procedure max(var ret: integer; a, b: integer);
begin
  if (a>b) then
    ret := a
  else
    ret := b;
end;

Dans les langages plus modernes, on utilise fréquemment l’instruction return qui permet d’indiquer que l’on souhaite renvoyer une valeur, mais il n’y a pas de nommage de l’espace utilisé pour renvoyer cette valeur et on en parle que rarement (voire on évite le sujet). Évidemment, on ne se pose plus la question de savoir comment ça marche, mais il est entendu que la valeur est transmise (comment ? Mystère et boule de gomme) de l’appelé à l’appelant. Il existe des tas de techniques pour le faire, une possible est l’emploi d’un registre ou d’un espace réservé sur l’image courante de la pile (stack frame). Les autres techniques ne changent rien à l’affaire que l’on souhaite décrire.

Par conséquent lorsqu’on écrit x=f(y), l’appelant réserve un espace pour que l’appelé y dépose la valeur calculée, puis ensuite l’appelant copie cette valeur dans celle désignée par x. La RVO consiste donc simplement à éviter la dernière copie en employant directement x pour stocker la valeur calculée!

Autrement dit, la RVO consiste simplement à, dans le cas de Pascal, utiliser x comme véritable espace de stockage désigné par l’identificateur de la fonction!

Si l’on souhaite réaliser les choses à la main il suffit donc de transmettre à la fonction l’emplacement où stocker la valeur.

Étant donné une fonction C (ici on code en C, monsieur!):

int max(int a,int b) {
  if (a>b) return a
  else     return b;
}

Il suffit de la traduire comme:

void maxRVO(int *ret,int a,int b) {
  if (a>b) *ret = a;
  else     *ret = b;
}

Ainsi un appel comme x=max(u,v) est réalisé par RVO via un appel à maxRVO(&x,u,v). Si vous voulez être proche de Pascal alors:

void maxRVO(int *maxRVO,int a,int b) {
  if (a>b) *maxRVO = a;
  else     *maxRVO = b;
}

fera votre bonheur.

Dans le cas de C++ et des objets c’est un poil plus compliqué car il faut prendre en compte la construction de l’objet à retourner et la traduction n’est pas aussi simple à la main, mais le compilateur possède tout ce qu’il faut pour s’en sortir. Une tentative naïve pourrait être la suivante :

A &fRVO(A &ret) {
  return *new (&ret) A(2); // placement new
}
// get uninitialized storage for object of type A
typename std::aligned_storage<sizeof(A)>::type _storage;
A b = *(A *)&_storage;
// RVO on b
fRVO(b);

On remarquera que la technique consistant à transmettre un paramètre permettant de stocker la valeur calculée est tout à fait similaire à celle permettant de simuler l’appel d’une méthode dans un langage non objet; le paramètre est simplement la structure sur laquelle doit agir la fonction, il suffit de nommer ce paramètre this pour faire illusion. Ainsi on se retrouve dans le même monde, dans les deux cas, la fonction agit sur un contexte qui lui est fourni, ce contexte pouvant être un objet ou une variable.

Ah mais du coup on doit pouvoir faire un truc comme ça en Swift puisqu’on dispose de l’extension des types ? De sorte que le contexte «objet» soit exactement la variable que l’on souhaite modifier. Ben oui :

extension Int {
  mutating func max(_ a: Int,_ b: Int) {
    if a>b { self = a }
    else   { self = b }
  }
}

var a = 10
a.max(4,5)

Bon, je l’avoue ce n’est pas une façon très élégante d’écrire a=max(4,5), mais c’est pour illustrer le propos, puisque cela revient à avoir une fonction Swift :

func max( _ this: inout Int, a: Int, b: Int) {
  if a>b { this = a }
  else   { this = b }
}

max(a,6,7)

RVO, RVO, RVO, doudou, RVO, RVO, RVO, doudou…

Djib’

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.