RVO e NRVO em C++

RVO em C++

Você já ouviu falar em RVO em C++? E sobre NRVO em C++? Também não? Bom, se você é um programador e trabalha com C++, essa é uma funcionalidade absolutamente necessário de se conhecer. Então fica ligado que eu vou explicar nesse post o que é o Return Value Optimization e como se beneficiar dele em C++.

O que é RVO em C++?

Para entender o que é o RVO em C++, acredito ser de grande ajuda considerar a seguinte situação: você está escrevendo uma função e se pega pensando na melhor forma de retornar um objeto desde essa função evitando cópias desnecessárias. Você, que acabou de descobrir a semântica de deslocamento em C++ (rvalue references e move operators, essas coisas), pensa então em fazer o seguinte: mover o retorno da função para evitar a cópia do valor de retorno (como no código abaixo). Esperto, não? Bom, depende.


Exemplo 1 – movendo o retorno de uma função

#include <iostream>
#include <string>

std::string pray() {
  std::string prayer {"Crux Sacra Sit Mihi Lux\n"
                        "Non Draco Sit Mihi Dux\n" 
                        "Vade Retro Satana..."};
  return std::move(prayer);
}

int main() {
  // No NRVO anymore; move instead (or copy if move
  // is not supported by the type of the obj.
  std::string prayer {pray()};
  std::cout << prayer << std::endl;
  return 0;
}

Crux Sacra Sit Mihi Lux
Non Draco Sit Mihi Dux
Vade Retro Satana…


O problema

Certamente, em princípio é melhor mover do que copiar um objeto (se você não for mais utilizá-lo após a operação de deslocamento). Todavia, quando se trata do retorno de funções, acredito que desde o C++11, não é mais preciso fazer esse “hack” de mover o valor para fora da função devido à funcionalidade de Otimização de Valor de Retorno (ou RVO – Return Value Optimization – em inglês). Além disso, mover o objeto para fora da função evitará a utilização da RVO pelo compilador.

A solução – RVO em C++

A RVO em C++ determina que ao ser retornado de uma função qualquer, um objeto temporário de escopo local é automaticamente construído em seu lugar de destino através de um mecanismo de elisão de cópia (ou seja, nenhuma cópia ou deslocamento é realizada). Assim, basta retornar direto o seu objeto desde a função, como mostrado no exemplo a seguir, onde retorno prayer diretamente e utilizo esse valor para inicializar a minha variável da main – zero cópias e deslocamentos. Neste caso, quando há uma variável definida que está sendo retornada, chamamos a RVO de NRVO (do inglês Named Return Value Optimization), que significa Otimização de Valor de Retorno Nomeado. Uma beleza, sim 👍 ?


Exemplo 2 – NRVO em C++

#include <iostream>
#include <string>

std::string pray() {
  std::string prayer {"Crux Sacra Sit Mihi Lux\n"
                        "Non Draco Sit Mihi Dux\n" 
                        "Vade Retro Satana..."};
  return prayer;
}

int main() {
  // NRVO (Named Return Value Optimization), no copy
  std::string prayer {pray()};
  std::cout << prayer << std::endl;
  return 0;
}

Crux Sacra Sit Mihi Lux
Non Draco Sit Mihi Dux
Vade Retro Satana…


URVO em C++

Gostou do NRVO? Sim? Sabe o que é melhor? Que isso não é tudo! A RVO também funciona para valores retornados que não possuem nomes 😎 – como string literals (veja o exemplo 3 abaixo), por exemplo. O mesmo mecanismo de elisão de cópia acontece nesse caso, com a diferença que dessa vez chamamo-lo de URVO (Unnamed Return Value Optimization, ou Otimização de Valor de Retorno Sem Nome – ou não nomeado).


Exemplo 3 – URVO em C++

#include <iostream>
#include <string>

std::string pray() {
  return {"Crux Sacra Sit Mihi Lux\n"
                        "Non Draco Sit Mihi Dux\n" 
                        "Vade Retro Satana..."};
}

int main() {
  // URVO (Unnamed Return Value Optimization), no copy
  std::string prayer {pray()};
  std::cout << prayer << std::endl;
  return 0;
}

Crux Sacra Sit Mihi Lux
Non Draco Sit Mihi Dux
Vade Retro Satana…


Cuidado com os ternários!

Para concluir, há um caso bastante comum no qual a RVO não é realizada pelo compilador, então fique atento à armadilha 🚨 ❗

Se você utilizar um operador ternário no retorno da sua função (como no exemplo 4 abaixo), a RVO não é realizada por haverem duas variáveis involvidas na inicialização do objeto que receberá o valor retornado. Ao invés de usar um operador ternário, você pode usar um if-else “clássico” e retornar os valores que deseja dentro de cada um deles conforme a lógica em questão – dessa forma, a RVO poderá ser utilizada normalmente e você colherá seus benefícios.


Exemplo 4 – operador ternário previnindo a ação da RVO em C++

#include <iostream>
#include <string>

std::string pray(bool firstPart = true) {
  std::string prayerFirstPart {"Crux Sacra Sit Mihi Lux.\n"
                        "Non Draco Sit Mihi Dux.\n" 
                        "Vade Retro Satana!"};
  std::string prayerSecondPart {"Nunquam Suade Mihi Vana.\n"
                        "Sunt Mala Quae Libas.\n" 
                        "Ipse Venena Bibas."};
  return firstPart ? prayerFirstPart : prayerSecondPart;
}

int main() {
  // No NRVO anymore; move instead (or copy if move
  // is not supported by the type of the obj.
  std::string prayer {pray()};
  std::cout << prayer << std::endl;
  return 0;
}

Crux Sacra Sit Mihi Lux
Non Draco Sit Mihi Dux
Vade Retro Satana…


Recapitulativo

Vimos neste artigo que a Otimização do Valor de Retorno, ou RVO, em C++ permite que nenhuma cópia ou deslocamento seja realizada quando se retorna um valor de um função. Isso se dá através de um mecanismo chamado de elisão de cópia. Vimos também que a RVO funciona para valores retornados que possuem nomes (chama-se então NRVO), e para valores sem nomes (URVO, nesse caso).

A seguir estão listados os demais pontos principais dos quais tratei nesse artigo:

❌ Não mova (std::move) o retorno de suas funções, pois isso evitará a RVO e causará o deslocamento do objeto retornado;

✅ Retorne diretamente os objetos desejados, ainda que eles sejam objetos temporários e sem nome – assim você se beneficiará da RVO;

➡️ Utilize a estrutura if-else clássica e evite usar operadores ternários no retorno de suas funções se deseja aproveitar os benefícios da RVO.

Referências

Se quiser saber mais sobre o RVO e NRVO em C++, sugiro que consulte as páginas 303-304 do livro C++ Profissional, de Marc Gregoire.

Gostou do artigo? Então inscreva-se na nossa newsletter para não perder nenhum dos nossos artigos 🙂

Foto de perfil de Emanoel

Sou apaixonado por tecnologia, literatura e também filosofia. O cultivo dessas paixões ao longo da minha trajetória me inspiraram a compartilhar aquilo que aprendo com os outros da maneira mais clara que eu possa. Sou formado em Engenharia Elétrica no Brasil, e também sou engenheiro formado na França. Trabalho atualmente como programador C++ em uma multinacional francesa, uma das maiores empresas de TI do mundo.

Leave a Reply

Your email address will not be published. Required fields are marked *