Menu
Coddy logo textTech

Perfect Forwarding

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 84 de 104.

Ao escrever funções de template que aceitam argumentos e os passam para outras funções, surge um problema: como preservar se o argumento original era um lvalue ou rvalue? O Perfect forwarding resolve isso passando os argumentos exatamente como foram recebidos, mantendo sua categoria de valor.

Os ingredientes principais são referências de encaminhamento (escritas como T&& em um contexto de template) e std::forward. Uma referência de encaminhamento pode se vincular tanto a lvalues quanto a rvalues, e std::forward converte condicionalmente o argumento de volta para seu tipo original:

#include <iostream>
#include <utility>

void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main() {
    int n = 10;
    wrapper(n);       // Chama process(int&) - lvalue preservado
    wrapper(20);      // Chama process(int&&) - rvalue preservado
}

Sem o std::forward, parâmetros nomeados são sempre lvalues dentro da função, mesmo que um rvalue tenha sido passado. Isso impediria que a semântica de movimento funcionasse corretamente ao encaminhar para construtores ou outras funções.

O perfect forwarding é essencial para escrever funções de fábrica, classes wrapper e qualquer código genérico que precise construir objetos ou invocar funções, preservando a intenção do chamador em relação a movimentações versus cópias.

challenge icon

Desafio

Fácil

Vamos construir uma função de fábrica que demonstra o encaminhamento perfeito (perfect forwarding) ao construir objetos, preservando a categoria de valor dos argumentos passados a ela. Você criará uma função genérica make_object que encaminha argumentos para o construtor de uma classe, garantindo que lvalues sejam copiados e rvalues sejam movidos.

Você organizará seu código em três arquivos:

  • Widget.h: Defina uma classe Widget que rastreia como ela recebe seus dados.

    Seu Widget deve armazenar um nome std::string e um valor int. Forneça dois construtores:

    • Um que recebe const std::string& (referência lvalue) e int — imprime Widget constructed (copy): [name]
    • Um que recebe std::string&& (referência rvalue) e int — imprime Widget constructed (move): [name] e move a string

    Adicione também um método display() que imprime [name]: [value].

  • Factory.h: Crie sua função de fábrica com encaminhamento perfeito.

    Escreva uma função de template make_object que aceite uma referência de encaminhamento (forwarding reference) para o nome e um int regular para o valor. Use std::forward para passar o argumento do nome para o construtor de Widget, preservando sua categoria de valor original.

    A função deve retornar o Widget construído por valor.

  • main.cpp: Leia duas entradas:
    1. Um nome (string)
    2. Um valor (inteiro)

    Demonstre o encaminhamento perfeito criando dois widgets:

    1. Primeiro, armazene o nome em uma variável local e passe-o para make_object como um lvalue
    2. Segundo, passe uma string temporária (o mesmo nome concatenado com "_temp") diretamente para make_object como um rvalue

    Após criar cada widget, chame seu método display().

Por exemplo, com as entradas Gadget e 42:

Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42

Com as entradas Device e 100:

Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100

A ideia principal é que sua função make_object não sabe se recebeu um lvalue ou rvalue — mas, ao usar std::forward, ela preserva essa informação ao chamar o construtor de Widget. Isso permite que a resolução de sobrecarga do construtor selecione a versão apropriada, permitindo movimentos eficientes para temporários enquanto copia variáveis nomeadas com segurança.

Folha de consulta

O encaminhamento perfeito (perfect forwarding) permite que funções de template passem argumentos para outras funções, preservando sua categoria de valor original (lvalue ou rvalue).

O mecanismo utiliza referências de encaminhamento (T&& em contexto de template) combinadas com std::forward:

#include <utility>

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

Uma referência de encaminhamento pode se vincular tanto a lvalues quanto a rvalues. std::forward<T> converte condicionalmente o argumento de volta ao seu tipo original.

Sem o std::forward, parâmetros nomeados dentro de uma função são sempre tratados como lvalues, mesmo que um rvalue tenha sido passado. Isso impediria que a semântica de movimento (move semantics) funcionasse corretamente.

Exemplo demonstrando a preservação da categoria de valor:

void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int n = 10;
wrapper(n);       // Chama process(int&) - lvalue preservado
wrapper(20);      // Chama process(int&&) - rvalue preservado

O encaminhamento perfeito é essencial para funções de fábrica (factory functions), classes wrapper e código genérico que constrói objetos ou invoca funções, preservando a intenção do chamador em relação a movimentos versus cópias.

Experimente você mesmo

#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"

using namespace std;

int main() {
    // Ler entrada
    string name;
    int value;
    cin >> name >> value;
    
    // TODO: Criar o primeiro widget passando name como um lvalue
    // Armazenar o nome em uma variável local e passá-lo para make_object
    
    
    // TODO: Chamar display() no primeiro widget
    
    
    // TODO: Criar o segundo widget passando uma string temporária como um rvalue
    // Passar (name + "_temp") diretamente para make_object
    
    
    // TODO: Chamar display() no segundo widget
    
    
    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos